cthub技能树_web_RCE之命令注入

[toc]

命令注入

image-20200730205313234

进入题目,题目中给出了源码:

image-20200730205758156

我们随便输入一个ip看看输出结果:

image-20200730205859577

可以看到返回了执行ping -c 4 127.0.0.1的结果。同时发现这个网页使用的是get请求:

image-20200730210440289

那如果我们可以让服务器不执行ping命令,而执行我们需要的命令让服务器将结果回显,不就相当于我们有了一个webshell吗!这就是命令注入的意思。我们可以通过管道符来实现。

linux支持多种管道符:

管道符用法
;执行完前面语句再执行后面的。如ping 127.0.0.1; ls
|显示后面语句的执行结果。如ping 127.0.0.1 | ls
||前面语句出错时执行后面语句。如ping 127.0.0.1 || ls
&前面语句为假则执行后面语句。如ping 127.0.0.1 & ls
&&前面语句为假则报错,为真则执行后面语句。如ping 127.0.0.1 && ls

我们使用 |进行注入,可以看到返回了ls的结果:

image-20200730210948859

我们看看那个奇怪的php文件,发现没有回显,看看源码(这个地方坑了我很长时间,后面的题最终都需要查看源码),得到flag:

image-20200730211234543

过滤cat

image-20200730211439573

对于命令注入的题目,主要考察的就是各种过滤的绕过,这道题就是考察cat过滤的绕过。

首先查看flag文件名:

image-20200730221846743

读取文件可以使用more、head等命令。关于linux的文本读取命令,可以看看这篇文章:Linux读取文本常用命令。本题我使用more命令,得到flag

image-20200730222246812

由于后面的题目均考察不同内容的过滤,所以非重点的图片等就不再放上来了。

过滤空格

这次我们要绕过空格,绕过空格有很多种方法。我们可以使用我们可以使用${IFS}来表示空格,IFS是shell中的一个变量,关于IFS的资料,可以看这篇文章(强烈推荐看看):详细解析Shell中的IFS变量

我们在命令中就用IFS来替换空格:

image-20200730223159084

得到flag:

image-20200730223441299

过滤目录分隔符

目录分隔符/我们可以使用$HOME代替,HOME也是shell中的一个环境变量,表示当前用户的根目录,我们可以看看当前用户的HOME值是什么

image-20200731173205285

可以看到当前用户的根目录是/home/www-data,我们只需要/,所以我们可以用${HOME:0:1}来实现

image-20200731181313922

首先查看flag位置:

image-20200731172616367

image-20200731172934380

读取flag:

image-20200731181418868

image-20200731181428086

过滤运算符

这道题过滤了 | 和 &,我们可以使用 ; 进行注入:

image-20200731181801768

直接cat得到flag:

image-20200731181837629

综合过滤练习

首先看一下过滤的符号有哪些:

image-20200731181939756

过滤了 | & ; 空格 / cat flag ctfhub这些符号

我们可以使用%0a(换行符的url编码)来绕过运算符:

image-20200731182638296

字符串的绕过我们可以使用反斜杠 \,如flag变成fl\ag:

image-20200731182804769

读取flag:

image-20200731183029022

image-20200731183037672

至此,我们就完成了命令注入的技能树,可以看到命令注入主要考察的就是各种绕过姿势,本文主要针对题目来讲,这里再放一些命令注入绕过姿势的总结文章,供大家参考和学习:

命令注入绕过姿势

命令执行绕过小技巧

命令注入绕过技巧总结

关于命令执行/注入 以及常见的一些绕过过滤的方法

ctfhub技能树_web_RCE之eval、文件包含

[toc]

这次我们来看看RCE(远程代码/命令执行)吧。由于篇幅限制,这篇文章不包含命令注入,命令注入我会在另一篇文章中详细的记录。

eval执行

image-20200730155557512

打开题目,可以看到网页的源代码

image-20200728222754175

通过代码可以看到,这就是典型的web后门,配置中国蚁剑

image-20200730153921884

进入后台,获取flag

image-20200730153821908

文件包含

image-20200728223933664

看看代码:

image-20200728223945677

strpos(string,find,start) 函数查找字符串在另一字符串中第一次出现的位置

参数描述
string必需。规定要搜索的字符串。
find必需。规定要查找的字符串。
start可选。规定在何处开始搜索。
返回值:返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。
注释:字符串位置从 0 开始,不是从 1 开始。

可以看到如果get传入参数file的值开头为xflag(x为任意字符),则执行include()函数。

include (或 require)语句会获取指定文件中存在的所有文本/代码/标记,并复制到使用 include 语句的文件中。

服务器后台有一句话木马shell.txt,因此我们的目标就是让服务器执行shell.txt中的语句。所以file的值为shell.txt

看看shell:

image-20200728224421429

则shell要传的参数是ctfhub:

image-20200730154818635

查找flag:

find / -name flag*

image-20200730154859368

得到flag:

image-20200730154918100

php://input

image-20200728230112933

首先看代码:

image-20200728230147871

可以看到如果get的参数file前六个字符为”php://“则执行include函数

看看phpinfo

image-20200728230338863

可以看到allow_url_include是On,说明可以使用php://input伪协议。

php://input 是个可以访问请求的原始数据的只读流。其实说白了它就是用来获取post内容的,但是其实只要你把内容写到请求包中,post和get都能获取。

那我们就通过这个伪协议和精心构造的请求包来获取我们想要的信息(注意此时虽然我是get请求而不是post,但由于我的包中有内容,所以伪协议依然是接收到了):

image-20200728231301739

这样我们就可以来找我们要的flag了

image-20200728231554765

有了flag的路径,就可以得到flag了。

image-20200728231744426

远程包含

image-20200728231924149

看代码:

image-20200728232001224

这个题的解法和上一题php://input一模一样,不再赘述。

image-20200728232316912

读取源代码

image-20200728232832725

代码审计:

image-20200728232902587

首先尝试php://input,发现没有返回结果

image-20200728233102208

测试了好多遍都无果,那看来是没法用input了。再看题目,题目告诉我们了flag的路径,于是我们可以用另一条伪指令php://filter来进行读取

php://filter是一种元封装器, 设计用于数据流打开时的筛选过滤应用。简单理解就是个可以读取数据的过滤器。我们可以用它选择想要进行操作并读取的内容。

php://filter 目标使用以下的参数作为它路径的一部分。 复合过滤链能够在一个路径上指定。详细使用这些参数可以参考具体范例。

名称描述
resource=<要过滤的数据流>这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表>该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表>该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表>任何没有以 read=write= 作前缀 的筛选器列表会视情况应用于读或写链。

关于php://filter更多的妙用可以看这个大佬的文章

知道了php://filter的用法,我们就可以读取flag了。

image-20200728232928462

关于伪协议部分,这里是官方的文档

至此,我们初步了解了文件包含和eval执行,解锁了对应的技能树,下一步,我们就来看看命令注入吧。

ctfhub技能树_web_web前置技能之http协议

[toc]

[begin]这[/begin]篇文章是ctfhub write up系列的第一篇,ctfhub这个平台在知名的ctf练习平台中算是相对比较基础的,我觉得它很好的一点是它的技能树由浅入深(只是除了web其他几乎都没几道题啊……),非常适合刚入门的ctfer。这个系列呢就来刷一下ctfhub技能树,wp也会按照技能树的节点来整理。当然,篇幅限制,这个系列不会详细的解释题目背后的基础原理。好了,废话不多说,让我们首先从web开始吧!

初见web,当然要了解web中常用的前置技能中最基础之HTTP协议。

请求方式

image-20200728214356549

打开题目,题目描述如下:

image-20200728214449865

让我们用CTF**B,这一猜就是CTFHUB嘛。(我不会告诉你我试了很多遍CTF**B)

burp抓包把报头的GET改成CTFHUB再发送,得到flag:

image-20200728214927838

302跳转

image-20200728215000780

打开题目

image-20200728215119561

点一下give me flag还是这个界面,题目提示我们302跳转,那点击按钮的时候用burp抓包看看:

image-20200728215219830

可以看到有一个“一闪而过”的302跳转界面,而这里就有我们要的flag

Cookie

image-20200728215341660

进入题目:

image-20200728215415635

所以我们要伪造admin身份,burp抓包看看:

image-20200728215513014

cookie的值本来是admin=0,我们改成1再请求下就可以得到flag

基础认证

image-20200728215735900

进入题目,点击按钮,发现让我们输入用户名和密码,注意验证中的“Do u know admin ?”

image-20200728215702867

题目中还有个附件,我们下下来看看,发现是个字典

image-20200728215909065

那看来让我们爆破了,题目中有“Do u know admin ?”,那我们就猜用户名是admin,用burp开始爆破吧,首先burp抓包看看(用户名随便填的wwww,密码随便填的ww):

image-20200728220401932

发现有base64加密,解密后发现是wwww:ww,那我们就知道了请求的格式了:“用户名:密码”。

准备爆破,注意设置规则,先添加前缀admin:,再进行base64加密得到payload,设置完成后用题目给的字典开始爆破。

image-20200728220822212

爆破结果:

image-20200728221049282

可以看到有一次的结果和其他结果返回的数据包长度不一样,打开响应包,得到flag

响应包源代码

image-20200728221211252

打开是个贪吃蛇游戏,不过我们不管他,看看网页的源码:

直接得到flag

至此,http协议的技能树我们就全解锁啦,继续向青草更青处漫溯吧!

攻防世界web新手训练

攻防世界web新手

[toc]

view_source

F12看源码

image-20200717105543297

get_post

进入链接后提示如下:

image-20200717105042516

构造url

image-20200717105235135

提交后提示如下:

image-20200717105348157

再次构造url

image-20200717105410671

提交得到flag

image-20200717105433487

robots

进入链接后什么都没有

看看robots.txt

image-20200717105816695

可以看到disallow的位置就是我们要找的flag

image-20200717105903647

backup

image-20200717110737642

看到index.php,我们看看

image-20200717110832971

发现没有什么变化,那么根据提示,我们看看备份文件,一般备份文件的后缀是.bak

image-20200717110927693

当我们输入index.php.bak时可以下载该文件,下载后打开,得到flag

image-20200717111007209

cookie

image-20200717111232613

抓包看看

image-20200717111309876

那我们就看看cookie.php

image-20200717111344601

提示我们看看响应包,得到flag

image-20200717111438468

disabled_button

image-20200717113546553

看看这个按钮

image-20200717113711469

可以看到这个按钮的作用实际上就是post传递一个auth=flag,那么我们自己传这样一个参数,得到flag

image-20200717113819628

weak_auth

进入后是一个登陆界面,随便输一个用户名密码

image-20200717114325671

说明用户是admin

image-20200717114352984

可以看到这个题就是暴力破解,那么放到burp suite里跑个字典

image-20200717114500113

可以看到密码就是123456

image-20200717114523421

得到flag

simple_php

image-20200717114651376

看php代码说明我们要构造a和b,让a和b的值都满足条件,就可以获得flag

那么首先看a,要a=0为真,a为真,所以a不可能等于0,要想解决这个问题,首先我们要知道php的特性。

由于php为弱类型语言,因此当不同类型的值进行==比较的时候会发生类型转换。正常情况下不同类型的值是不能比较的,php 为了比较进行了数据类型转换。把不同类型的值转换为相同类型后再比较。

具体转换规则可以看这篇文章《彻底解决php判断a==0为真引发的问题-类型转换》,这里不再赘述。

知道了转换规则,那么我们就可以构造了,得到flag

image-20200717115934924

xff_referer

进入后提示如下:

那么我们就在请求包中通过x-forwarded-for伪造ip

image-20200717162612014

注意xff加入的位置,我之前加入到末尾发现页面一直在加载没有响应,我想xff写在请求头中间就好了。

然后发现提示变化:

image-20200717161539728

那么在伪造referer

image-20200717162711288

得到flag

image-20200717162734141

webshell

image-20200717170244887

php的一句话木马,使用中国菜刀连接,中国菜刀可以从这个地方下载,

image-20200717170319974

即可进入服务器

image-20200717170345624

获得flag

image-20200717170404876

当然,如果没有工具,那就手动构造请求post包

一句话木马中的变量是shell,所以我们传的变量就是shell

image-20200717170710704

可以看到返回了当前路径的所有文件,看到了flag.txt

image-20200717170754480

得到flag

command_execution

image-20200717172147730

尝试ping一下本机127.0.0.1

image-20200717172220593

可以看到ping的结果返回到了页面,那么尝试命令注入

命令注入有很多种方法,可以参考这两篇文章:文章1文章2,这里不再赘述

这道题比较简单,没有waf,不需要绕过,我使用 | 进行注入

command 1 | command 2 只执行command2

image-20200717172637290

可以看到执行了ls命令,现在找flag

image-20200717172921373

得到flag

image-20200717172957802

simple_js

image-20200717181134028

随便输一个

image-20200717181148627

看看源码,调试一下js

image-20200717181218694

发现不论输入的密码是什么,最后都会跳到假密码FAUX PASSWORD HAHA

而真密码是初始的pass_enc

image-20200717181342602

那么就把pass的值改为pass_enc,执行一下,注意14行的tab2下标要改成10,因为默认的pass长度为18,而修改后的pass长度为11,所以要修改,否则最后执行的时候p加入的就不是pass的最后一个值了(这个地方坑了我好久)

image-20200717181443038

把获取到的字符串加上题目要求的flag格式即可得到flag

sqli-labs Less1-5题 Write Up

[toc]

Less-1

image-20200713154808415

输入一个id看看

image-20200713154939518

题目是单引号,因此id加一个单引号看看

image-20200713155040120

可以看到报错了。说明单引号被成功解析,那么我们就通过闭合单引号来进行注入。

首先通过order by判断字段:

image-20200713155238806

不断提高order by后面的数值

image-20200713155311592

可以看到order by到4的时候报错了,说明字段只有3个。

接下来通过union看看页面显示的内容对应的位置

image-20200713155813890

页面成功执行,但是返回的内容没有变化,说明页面只会返回第一条结果(id=1),因此我们可以把id变成系统中没有的(id=-1),让服务器返回select的结果

image-20200713155932512

可以看到页面返回了2和3,说明页面返回的是select的第二个和第三个地方,所以我们注入的地方就是2和3

接下来查询当前网站使用的数据库 database()、当前Mysql的版本 version()、当前Mysql的用户 user()

CONCAT_WS(separator,str1,str2,…),连接str1、str2等字符串,并在各字符串中以separator分隔

image-20200713160539637

接下来先查询服务器一共有多少个数据库

Mysql中默认存放一个information_schema的数据库,该库中,有三个重要的表,分别是schemata, tables和columns

schemata储存该用户创建的所有数据库的库名,库名字段为schema_name

tables储存该用户创建的所有数据库的库名和每个库中的表名,库名字段是table_schema,表名字段是table_name

columns储存该用户创建的所有数据库的库名、每个库的表名和每个库中每个表中的字段名,库名字段是table_schema,表名字段是table_name,字段名是column_name

image-20200713162304922

可以看到第一个表为information_schema,修改limit 0,1中0的值,遍历

image-20200713162401207

最终得到所有的库名。

然后查询数据库中的表名,以security为例

image-20200713163201802

遍历查询,就得到所有的表名。

然后获取表的字段名,以emails为例

image-20200713164353082

遍历可以得到emails的所有字段id,email_id

然后获取字段内容

image-20200713165034010

遍历

image-20200713165402331

可以看到一共有8条数据

image-20200713165624149

通过这样我们就可以把整个服务器的数据库down下来。

补充:

我们可以使用group_concat将信息输出到一行,这样更为方便。

image-20200713170544482

Less-2

image-20200713171603173

注入单引号,根据报错信息可以看出id在sql中是整型

image-20200713171622650

通过布尔条件测试判断能否注入

image-20200713171850699

image-20200713171924317

得出结论存在注入,使用union注入

image-20200713172017996

查询数据库名

image-20200713172117403

注入成功,后续爆库步骤省略。

Less-3

单引号测试,查看报错信息

image-20200713172910823

猜测sql语句形式为

SELECT * FROM * WHERE id=('$id') LIMIT 0,1

布尔测试

image-20200713173111249

image-20200713173120963

可以注入,注意构造的形式

image-20200713173536293

爆库

image-20200713173648836

Less-4

双引号测试

image-20200713174125565

猜测sql语句

SELECT * FROM * WHERE id=("$id") LIMIT 0,1

布尔测试

image-20200713174333657

union注入:

image-20200713174422863

爆库

image-20200713174656504

Less-5

单引号测试

image-20200713174921832

布尔测试

image-20200713174941031

image-20200713174949880

可以看到存在注入,但是当布尔值为真时页面没有回显,因此考虑布尔注入

首先判断该数据表的字段

image-20200713175252435

image-20200713175301836

可以看到一共有三个字段。

判断数据库名字的长度

使用length()函数

可以看到长度大于等于1

image-20200713175615810

小于15

image-20200713175702619

多次测试后发现数据库名长度为8

再按位测试数据库每一位的值

使用substr(str,a,b)函数,a是从第几个字符开始(注意:字符从1开始,不是从0开始),b是每次返回几个字符

image-20200713180012852

当测试到s时返回真,则数据库名的第一个字符为s

image-20200713180044267

通过这种方式可以进行爆库。

实验吧CTF——头有点大

[toc]

题目解析

解题链接:http://ctf5.shiyanbar.com/sHeader/

点开题目:

1562932892121

这题提示很明显,看它的要求,明显就是要修改http的请求头伪造身份。

所以就按照它说的,IE浏览器,.net9.9,英国访问

User-Agent加入:

  • compatible;MSIE 6.0 表示IE浏览器
  • .NET CLR 9.9 表示.NET 9.9 (注意开头那个点!)

Accept-Language加入

  • en-gb 表示英国地区

修改好后提交请求头得到flag:

1562933538453

本题知识点

请求头伪造

0x01 IP伪造

在TCP/IP中,我们可以伪造数据包来源IP,但是这样会导致发出去的数据包返回到伪造的IP上(你发件人地址填错了,别人给你发回来的时候肯定就按照错的地址发了)。

但是在应用层协议HTTP上实现就比较容易了,通过伪造IP可以让非授权IP访问服务器,绕过IP地址过滤,甚至可以钻服务器的漏洞进行攻击。

例如在投票系统中,为了防止刷票,往往会限制IP投票次数,比如一个IP只能投2票,那么如果伪造IP,就可以绕过IP限制,进行刷票。

IP伪造的关键是X-Forwarded-For

关于XFF这里不细讲,想了解更多的同学可以自行学习。我们直接讲它的应用。

它的格式如下:

X-Forwarded-For: client, proxy1, proxy2

XFF包含多个IP地址,每个值通过逗号+空格隔开,最左边是最原始的客户端的IP,后面如果有多层代理,那么每一层代理会在后面追加它自己的IP。

实际上,一般的客户端(例如浏览器)发送HTTP请求是没有XFF头的,当请求到达第一个代理服务器时,代理服务器会加上XFF请求头,并将值设定为客户端的IP地址。

但如果客户端在发起请求时就带了伪造的XFF头,那么后续的代理服务器就只会追加而不会覆盖,最终服务器获取的左边的第一个IP就是我们自己伪造的IP,就达到了伪造的目的。

0x02 User-Agent伪造

User Agent中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA。它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核等信息的标识。通过这个标识,用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计。比如说手机用户和电脑访问的网页排版样式等都是不一样的,这就是通过UA检测出来的。

UA的语法如下:

User-Agent: <product> / <product-version> <comment>

product代表软件产品名称,product-version表示版本,comment表示一些其他信息。具体UA语法可以参照这篇文章《User Agent 趣事谈》IETF标准文档,这里不展开讲了。

一般最常见的伪造就是伪造浏览器类型。有时候还要求伪造操作系统或者组件的版本等等,具体根据题目要求进行伪造。本题就要求特定的浏览器,以及特定的.NET版本。

一般常见的UA标识可以参考这篇文章《User-Agent大全》,内容实在太多了就不放在这儿了。

0x03 地区伪造

实际上地区伪造是在Accept-Language请求头中完成的。

Accept-Language请求头允许客户端声明它可以理解的自然语言,以及优先选择的区域方言。

AL的语法如下:

Accept-Language: <language>
Accept-Language: <locale>
Accept-Language: *

// Multiple types, weighted with the quality value syntax:
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

language:用含有两到三个字符的字符串表示的语言码。

locate:完整的语言标签。除了语言本身之外,还会包含其他方面的信息,显示在中划线(”-“)后面。最常见的额外信息是国家或地区变种(如”en-US”)或者表示所用的字母系统(如”sr-Lat”)。其他变种诸如拼字法(”de-DE-1996″)等通常不被应用在这种场合。

* :任意语言;”*”表示通配符。

;q= (q-factor weighting)紧跟在所标识语言的后面:

值代表优先顺序,用相对质量价值表示,又称为权重。q是权重系数,范围 0 =< q <= 1,q 值越大,请求越倾向于获得其“;”之前的类型表示的内容,若没有指定 q 值,则默认为1,若被赋值为0,则用于提醒服务器哪些是浏览器不接受的内容类型。


Examples of language tags

Language familyLanguage tagLanguage variant
Banglabn-BDBangla (Bangladesh)
bn-INBangla (India)
Chinesezh-CNMainland China, simplified characters
zh-TWTaiwan, traditional characters
zh-HKHong Kong, traditional characters
Dutchnl-BEBelgian Dutch
nl-NLStandard Dutch (as spoken in The Netherlands)
Englishen-GBBritish English
en-USAmerican English
en-CACanadian English
en-INIndian English
en-AUAustralian English
en-NZNew Zealand English
Frenchfr-BEBelgian French
fr-CH“Swiss” French
fr-FRStandard French (especially in France)
fr-CACanadian French
Germande-ATAustrian German
de-DEStandard German (as spoken in Germany)
de-CH“Swiss” German
Italianit-CH“Swiss” Italian
it-ITStandard Italian (as spoken in Italy)
Portuguesept-PTEuropean Portuguese (as written and spoken in Portugal)
pt-BRBrazilian Portuguese
Spanishes-ESCastilian Spanish (as spoken in Central-Northern Spain)
es-MXMexican Spanish
es-ARArgentine Spanish
es-COColombian Spanish
es-CLChilean Spanish
es-USAmerican Spanish
Tamilta-INIndian Tamil
ta-LKSri Lankan Tamil

实验吧CTF——后台登录

[toc]

题目解析

解题链接:http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php

一进去给个登录界面:

1562929066459

F12打开源码看看,哦吼,给了php代码提示:

1562929144304

SELECT * FROM admin WHERE username = 'admin' and password = 'xxx' 

这个xxx就是md5($password,true),

这个语句很明显就是sql注入。那么我们就要构造xxx为 ‘ or ‘ 1…这种形式,让后面变成true。

构造password为ffifdyop,输出结果就为:’or’6�]��!r,��b

我们发现有 ‘ or ‘ 6…(后面乱码不用管)。这样就成功注入。

得到flag:

1562929036492

其实如果你仔细看这道题的url,你会有神奇的发现。

本题知识点

md5注入

md5(string,raw)

参数描述
string必需。规定要计算的字符串。
raw可选。规定十六进制或二进制输出格式:<br />true – 原始16字符2进制格式<br />false – 默认。32字符十六进制数

ffifdyop这个字符在16字符2进制格式中会变成一个特殊的格式

> ‘or’6�]��!r,��b

可以进行sql注入。

实验吧CTF——PHP大法

[toc]

题目解析

解题链接:http://ctf5.shiyanbar.com/DUTCTF/index.php

进入界面,只有简单的一句话:

1562926789229

给了个提示,叫index.php.txt,那就看看呗。url加个.txt,得到了php源码

具体源码如下:

&lt;?php
if(eregi(&quot;hackerDJ&quot;,_GET[id])) {
  echo(&quot;&lt;p&gt;not allowed!&lt;/p&gt;&quot;);
  exit();
}_GET[id] = urldecode(_GET[id]);
if(_GET[id] == &quot;hackerDJ&quot;)
{
  echo &quot;&lt;p&gt;Access granted!&lt;/p&gt;&quot;;
  echo &quot;&lt;p&gt;flag: *****************} &lt;/p&gt;&quot;;
}
?&gt;


&lt;br&gt;&lt;br&gt;
Can you authenticate to this website?

分析源码,它判断id是不是hackerDJ,如果是就显示“not allowed!”,然后对id进行解码再次判断它是不是hackerDJ。是了就返回flag。

这个题其实比较简单,就是输入的原始参数id不能为hackerdj(eregi()判断的时候不区分大小写),然后解码之后要让值为hackerDJ。由于浏览器url在传递参数的时候会进行一次decode,然后在源码里又进行一次decode,所以我们在构造id的时候应该encode两次。

网上在线url编码hackerDJ还是hackerDJ,送浏览器里不认,所以我们需要手动encode。

为了简单,就把开头的h编码两次就可以。

hackerDJ->%68ackerDJ->%2568ackerDJ

(当然你把68也encode,最后变成 %25%36%38ackerDJ 也是可以的。当然你也可以把所有的字符都进行两次encode,只要你不嫌麻烦)

传入参数id,得到flag

1562928157030

本题知识点

URL编码

巨简单,就是%加上字符对应的ASCII码(16进制)。

实验吧CTF——天网管理系统

[toc]

题目解析

解题链接: http://ctf5.shiyanbar.com/10/web1/

首先进入题目界面

1562823085465

先点一下登陆系统,又跳回到这个界面,看来用户名密码不对(废话)

那看看源码

1562823163856

哦吼,看到一个php的提示,要MD5加密后的$test == ‘0’,那这很常见啊,随便找一个MD5加密后是0e开头的就行了。

给个QNKCDZO(加密后的结果是0e830400451993494058024219903391)试一下

1562824378658

出来个这,我开始没仔细看,天真的以为flag已经出来了,把hjkleffifer当flag了😅

仔细一看,哦,原来是url后缀,好,把index.php改成出现的后缀,有了新发现

1562824822673

嗯,又是一段php代码,具体代码如下

$unserialize_str = $_POST['password'];
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' && $data_unserialize['pass'] == '???'){
    print_r($flag);
}

就是说要通过password传递参数让data_unserialize数组中的user项和pass项满足条件。但是现在不知道两处???是什么,所以没法直接构造相等的值。

但是还有一个提示。“成也布尔,败也布尔”。bool类型的true可以和任意字符串弱类型相等。所以我们只要让user和pass都为bool类型的ture就行了。

构造password的值:a:2:{s:4:”user”;b:1;s:4:”pass”;b:1;}

a代表array,s代表string,b代表bool,数字代表个数/长度(具体请参照反序列化知识点)。

提交,拿到flag。

1562825962891


本题知识点

PHP弱类型

PHP比较相等性的运算符有两种,一种是strict,一种是non-strict。”===”和”!==”即strict比较符,只有在类型相同时才相等。”==”和”!=”即non-strict比较符,会在类型转换后进行比较。

看以下的例子:

 true
    var_dump("1" == "01"); // 1 == 1 -> true
    var_dump("10" == "1e1"); // 10 == 10 -> true
    var_dump(100 == "1e2"); // 100 == 100 -> true
    var_dump(0 == '0'); // true
    var_dump(0 == 'abcdefg'); // true 
    var_dump(0 === 'abcdefg'); // false
    var_dump(1 == '1abcdef'); // true
?>

在和整数比较的时候,会把另一个参数强制转换成整数:

  • 如果参数是字符串,返回字符串中第一个不是数字的字符之前的数字串所代表的的整数值。
  • 如果字符串第一个是‘-’,则从第二个开始算起。
  • 如果参数是浮点数,则返回它取整后的值。

a是纯字符串,所以被转换成0。1abcdef被转换成1。

而e在比较的时候会被视为科学计数法,10的次方,1e2就是10的2次方。

关于布尔值的比较,见下表:

松散比较 ==

TRUEFALSE
1TRUEFAUSE
0FAUSETRUE
-1TRUEFAUSE
“1”TRUEFAUSE
“0”FAUSETRUE
“-1”TRUEFAUSE
NULLFAUSETRUE
array()FAUSETURE
“php”TRUEFAUSE
“”FAUSETRUE
TRUETRUEFAUSE
FALSEFAUSETRUE

从上表可以看出

当转换为bool型时,以下值被认为是FAUSE:

  • FAUSE本身
  • 整型值0
  • 空字符串
  • 字符串”0″
  • 不包括任何元素的数组(注意,一旦包含元素,就算包含的元素只是一个空数组,也是true)
  • NULL
  • 从空标记生成的SimpleXML对象

其余所有值都认为是TRUE。

具体请参见PHP类型比较表

所以当MD5加密后出现0exxxxxxxxxxxxxxxxxxxxxx之类的,如果用non-strict比较符,php就会当成0的多少多少次方(那当然结果还是0),和0比较就为true。因此可以利用这个漏洞进行验证绕过。

MD5加密后开头为0e的字符串举例:

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s1885207154a
0e509367213418206700842008763514

s1502113478a
0e861580163291561247404381396064

s1885207154a
0e509367213418206700842008763514

s1836677006a
0e481036490867661113260034900752

s155964671a
0e342768416822451524974117254469

s1184209335a
0e072485820392773389523109082030

s1665632922a
0e731198061491163073197128363787

s1502113478a
0e861580163291561247404381396064

s1836677006a
0e481036490867661113260034900752

s1091221200a
0e940624217856561557816327384675

s155964671a
0e342768416822451524974117254469

s1502113478a
0e861580163291561247404381396064

s155964671a
0e342768416822451524974117254469

s1665632922a
0e731198061491163073197128363787

s155964671a
0e342768416822451524974117254469

s1091221200a
0e940624217856561557816327384675

s1836677006a
0e481036490867661113260034900752

s1885207154a
0e509367213418206700842008763514

s532378020a
0e220463095855511507588041205815

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s214587387a
0e848240448830537924465865611904

s1502113478a
0e861580163291561247404381396064

s1091221200a
0e940624217856561557816327384675

s1665632922a
0e731198061491163073197128363787

s1885207154a
0e509367213418206700842008763514

s1836677006a
0e481036490867661113260034900752

s1665632922a
0e731198061491163073197128363787

s878926199a
0e545993274517709034328855841020

PHP关联数组

在本题中我们看到数组的下标不是数字,而是字符串,这是为什么呢?原来这是PHP的关联数组,如果你学过Python,那么你可以类比Python中的字典。PHP关联数组是使用用户分配给数组的指定键的数组。

//php中有两种创建关联数组的方法
//第一种
age=array("Bill"=>"35","Steve"=>"37","Elon"=>"43");
//第二种age['Bill']="63";
age['Steve']="56";age['Elon']="47";

然后在脚本中可以使用指定键:

"63","Steve"=>"56","Elon"=>"47");
echo "Elon is " . $age['Elon'] . " years old.";
?>
//运行结果:
//Elon is 47 years old.

如需遍历并输出关联数组的所有值,可以使用foreach循环:

"63","Steve"=>"56","Elon"=>"47");

foreach(age asx=>x_value) {
  echo "Key=" .x . ", Value=" . $x_value;
  echo "
"; } ?> /*运行结果: Key=Bill, Value=63 Key=Steve, Value=56 Key=Elon, Value=47 */

PHP中的序列化和反序列化

“序列化”,听着好像hin高大上,但实际上当你了解它之后会发现,其实它并不可怕。实际上PHP所谓的序列化实际上就是将各种类型的数据,压缩并按照一定格式存储。说白了就是压缩。使用的函数是serialize()。

看以下实例:

pri = private;
    }
    public function get_pri()
    {
        returnthis->pri;
    }
}

obj = new test();obj->set_pri('Active');
data = serialize(obj);
echo $data;

运行实例得出的结果是:

O:4:”test”:3:{s:9:”testpri”;s:6:”Active”;s:6:”*pro”;s:5:”test1″;s:3:”pub”;s:5:”test2″;}

看着好吓人!让我们一点一点看,慢慢你就会发现它其实很好理解。

Untitled (Draft)-3

如果你足够细心,你会发现private里明明我的属性名是pri,为什么这里是testpri,而且testpri不应该是7个字符吗,为什么这里是9个字符?protected中的名字和字符数也不一样。

这其实涉及到PHP的属性的访问权限问题,具体可以参照这篇文章:《一篇文章带你深入理解漏洞之PHP反序列化漏洞》。这里不做深入。我们只需要记住以下几点即可:

(1)Public权限

正常该是几个字符就是几个字符

(2)Protected权限

16进制打开看到属性名的格式为:

%00*%00属性名

在属性名前有一个 ***** ,而且 * 两边各有一个%00,每个%00占一个字符,所以字符数多了2。

(3)Private权限

16进制打开看到属性名格式为:

%00类名%00属性名

在属性名前面有这个类的名字,两边各有一个%00,所以字符数多了2。

这个特性必须记住,否则后期我们在构造或修改我们的攻击语句的时候就很容易出错。

如果你再细致一点,就会发现这个类不但有属性,还定义了许多的方法。可是序列化的只有属性,没有方法。这是为什么?

PHP的序列化只序列化属性,不序列化方法,这个性质引出了两个重要话题:

(1)在反序列化的时候一定要保证在当前的作用域环境下有该类存在

(2)反序列化攻击也就是依托类属性进行攻击

  • 因为序列化没有方法,所以我们只能控制类的属性,所以类属性是我们唯一的攻击入口。我们寻找合适的能被我们控制的类属性,然后利用它本身存在的方法,发动攻击。

看一个例子:

test = new L();
    }

    function __destruct()
    {
        this->test->action();
    }
}

class L
{
    function action()
    {
        echo "welcome to my PHP";
    }
}

class Evil
{
    vartest2;
    function action()
    {
        eval(this->test2);
    }
}

unserialize(_GET['test']);

这段代码就存在可以利用的漏洞。

在分析这段代码之前,我们首先看以下方法:

  1. construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
  2. wakeup() :unserialize()时会自动调用。
  3. destruct():当对象被销毁时会自动调用。
  4. toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用。
  5. get() :当从不可访问的属性读取数据。
  6. call(): 在对象上下文中调用不可访问的方法时触发。

我们发现有很多方法会被自动调用。这就给我们了可乘之机。回到上面的例子,我们分析这段代码,从上往下看,test类在构造对象时让test变量指向一个L的对象,然后在test对象销毁时调用了L对象的action()。

首先看我们可以控制的参数,也就是可以利用的只有$test,也就是test类,那么我们接着分析,__destruct()函数会在对象销毁时自动调用,那么它就会执行action()。我们再看在action()执行过程中有没有可以利用的,发现Evil类里面也有action(),而且它调用了eval(),那这样我们就有了攻击思路。

我们可以修改test变量的值,让它指向Evil的对象,这样在test对象销毁调用destruct()时就会调用Evil对象的action(),就会执行eval(),同时我们还可以再构造test2变量的payload,这样就可以进行攻击。

那么我们就可以构造序列化结果:

test = new Evil //让它指向Evil
    }
}
class Evil
{
    var test2 = "phpinfo();"; //payload为phpinfo(); 
}data = new test;
data = serialize(data);
echo $data

得到结果:

O:4:”test”:1:{s:10:”testtest”;O:4:”Evil”:1:{s:5:”test2″;s:10:”phpinfo();”;}}

将它送入$test,实现攻击。特别注意在构造的时候不要忘了两个%00

1562905573925

提交就可以执行我们的payload

1562905711077

这是一个很简单的示例,PHP反序列化漏洞的知识和利用方式还有很多,如果要深入展开实在是太多了。如果读者有兴趣可以参考这篇文章:《一篇文章带你深入理解漏洞之PHP反序列化漏洞》