作者:looch
前置知识
获取ThinkPHP 3.2.3
源码下载:
https://github.com/top-think/thinkphp/archive/3.2.3.zip
目录结构
index.php入口文件
Application应用目录
Public资源文件目录
ThinkPHP框架目录
其中ThinkPHP的结构如下:
ThinkPHP框架系统目录
Common核心公共函数目录
Conf核心配置目录
Lang核心语言包目录
Library框架类库目录
Think核心Think类库包目录
Behavior行为类库目录
OrgOrg类库包目录
Vendor第三方类库目录
...更多类库目录
Mode框架应用模式目录
Tpl系统模块目录
LICENSE.txt框架授权协议文件
logo.png框架LOGO文件
README框架README文件
ThinkPHP.php框架入口文件
一些方法介绍
A 快速实例化Action类库
B 执行行为类
C 配置参数存取方法
D 快速实例化Model类库
F 快速简单文本数据存取方法
L 语言参数存取方法
M 快速高性能实例化模型
R 快速远程调用Action类方法
S 快速缓存存取方法
U URL动态生成和重定向方法
W 快速Widget输出方法
I()方法
I('变量类型.变量名/修饰符',['默认值'],['过滤方法或正则'],['额外数据源'])
I('get.id') => $_GET["id"];
#而且,I()方法会自动进行htmlspecialchars过滤。
C()方法
读取已有的配置,配置文件里面的数据就可以通过C方法读取。
$model = C('URL_MODEL');
M()方法/D()方法
用于数据模型的实例化操作,具体这两个方法怎么实现,有什么区别,暂时就不多关注了,只用知道通过这两个快捷方法能快速实例化一个数据模型对象,从而操作数据库
$User = new ThinkModel('User')
// 相当于
$User = M('User');
ThinkPHP 3.2.3 sql注入
想要查看sql执行语句,我们需要开始sql语句监控:
在Thinkphp/Conf/debug.php
里面添加一行:
'SHOW_PAGE_TRACE'=>true,
执行语句之后:
注入一
在index下,接收一个参数,用内置的I()
方法。
public function index()
{
//$this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微软雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>欢迎使用 <b>ThinkPHP</b>!</p><br/>版本 V{$Think.version}</div><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_55e75dfae343f5a1"></thinkad><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script>','utf-8');
$data = M('user')->find(I('get.id'));
var_dump($data);
}
当我们传递参数后,观察sql语句,发现参数的单引号被过滤了
打下断点,查看过滤规则,是否可以绕过。
我们不需要进入M方法的,因为M方法是实例化数据库表,我们只需要关注他是从哪个地方把特殊字符给过滤的,并关注哪些参数可控。因此,我们需要先走一遍流程,阅读一下代码。
首先,传递一个有特殊字符的参数:
http://thinkphp-3.2.3/?id=2%27
进入I()
方法,首先是两个if
判断,其中,需要掌握的是:
strpos()
:查找字符串在某个子串中第一次出现的位置,返回所在下标,不存在返回空。explode()
:将字符串按照指定字符,分割成数组。
这里因为传入的name是get.id
,因此跳到第二个if
语句中,并且通过explode()
方法,将get.id
分割成数组array[0->"get",1->"id"]
,在分别赋值给变量$method
和$type
。
在进入到switch
方法中,因为$method=get
,因此将$_GET
的值(数组),赋值给$input
变量。因为get.id
是写死的,因此$method
是不可控的。但是$input
变量是可控的。
在赋值之后,就会跳到该处,将我们传入的url参数id所对应的值,赋值给$data
(可控)变量,继续往下走。走到下图的时候,我们可以看到,此时我们传入的特殊字符,还是没有被过滤。
当走到该处时,会对$data
进行判断,如果是数组,那么就会进入array_map_recursive
方法,如果不是,则用过滤器进行过滤,而array_map_recursive
方法也是一个递归过滤。过滤方式是将特殊字符转化为html实体类。
这里并不能命令执行,因为$filter
参数不可控。
继续往下走:
array_walk_recursive
方法是递归地遍历数组并应用回调函数到每个元素上。
也就是用think_filter
过滤我们传入的$data
数据。
看的出来,是在过滤一些关键字
走到最后的时候,我们的特殊字符还没有被过滤:
继续,接下来会进入到find()
方法。find()
方法会接受$data
数据,然后进行判断,如果是字符串或者数组,就会将$data
也就是$options
变成一个数组,数组键值中的键会有一个where
所对应的值就是我们传入的值$data
。
继续往下,到了下图这个地方的时候,我们发现数据还是我们传入的数据。
当再往下走一步的时候,突然直接就把特殊字符给过滤了:
那么问题就出现在_parseOptions
方法中,我们重新来一次,直接进入到该方法中,查看是否可以跳过。
进入到该方法后,我们简单阅读了一下,发现关键点在下图:
根据注释,以及理解的情况来看,这里会判断数组$options['where']
是不是数组,如果是,就进入该循环中,遍历$options['where']
数组,如果$options['where']
数组中的值是一个变量类型(布尔,int,string等等)只要不是数组,这样的,都会将该值通过_parseType
方法进行过滤。过滤方式为强转成int类型或者其他类型。
之后就进行查询:
那么问题来了,我们该如何绕过。
让where的值为数组
这显然不可能
让where不为数组
我们回忆到,在刚进入
find()
方法时,会对传入的值进行判断:
···php
if (is_numeric($options) || is_string($options)) {
$where[$this->getPk()] = $options;
$options = array();
$options['where'] = $where;
}
如果我们传入的$data
是字符串或者数组,那么就会进入到if语句中,并且让where
的值变为一个数组,那么我们跳过去,让$option为一个数组,不就跳过了?
那么该如何跳过呢?我们来追溯一下(这里是逆着来的):
数据传入逆推:
$option=>$data=>$input[$name]=>$input=>&$_GET
当传入id=1时,
$input=['id'=>1]
,$name=id
$input[$name]=1=$data=$option
,那么,我们如果传入的是id[where]=1
呢?此时就变成了:
$input=['id'=>['where'=>1]]
,$name=id
$input[$name]=['where'=>1]=$data=$option
,即跳过!
尝试:
报错,即可进行注入!
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...