高级篇主要讲
1. 熟知各个开源框架历史版本漏洞。
2. 业务逻辑漏洞
3. 多线程引发的漏洞
4. 事务锁引发的漏洞
在高级篇审计中有很多漏洞正常情况下是不存在的只有在特殊情况下才有
PHP常用框架
Zendframwork,Yii,Laravel ,、ThinkPHP
这里举例因为thinkphp由国内人开发用户量较多而且历史漏洞也多
Thinkphp历史漏洞很多,对于漏洞形成原因可以自己复现。
篇幅有限只介绍披露漏洞
Query方法 低于3.1.3 有sql注入问题
Order方法 低于 5.x 有sql注入问题
Update方法 低于3.2.3 有sql注入问题
/** * 更新记录 * @access public * @param mixed $data 数据 * @param array $options 表达式 * @return false | integer */ public function update($data,$options) { $this->model = $options['model']; $this->parseBind(!empty($options['bind'])?$options['bind']:array()); $table = $this->parseTable($options['table']); $sql = 'UPDATE ' . $table . $this->parseSet($data); if(strpos($table,',')){// 多表更新支持JOIN操作 $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:''); } $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:''); if(!strpos($table,',')){ // 单表更新支持order和lmit $sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'') .$this->parseLimit(!empty($options['limit'])?$options['limit']:''); } $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); }
5. x 版本有命令执行漏洞
在github上也有历史分支可以查看修复代码
业务逻辑
想要对整体的逻辑进行审计
- 熟悉业务场景
- 熟悉业务流程
- 通读代码
多线程引发的漏洞
这里我写了个例子
php $money=100;//数据库查询的用户余额 $buy=intval($_GET['buy']); if ($money>0&& $money-$buy>0) { sleep(10); $moeny-=$buy; //写入数据库 } return $money
正常情况下用户余额一定不为负数 如果在并发情况下呢?
用户发送恶意并发请求时就有可能出现这种情况。这么防御呢
这里需要知道事务和锁的概念可以自行百度理解我这里简单概述一下
事务:类似一个执行任务 成功就任务完成 ,失败任务自动回滚到未接任务前
锁:悲观锁,乐观锁。
我们可以把多线程请求变成单线程处理,这里也可以用队列压入压出。
php $money = 100;//数据库查询的用户余额 $buy = intval($_GET['buy']); try { if (flock($money, LOCK_EX)) { if ($money > 0 && $money - $buy > 0) { sleep(10); $moeny -= $buy; //写入数据库A throw new ExceptionNew("xp"); //写入数据库B } flock($money, LOCK_UN); } } catch (Exception $exceptione) { throw new ExceptionNew("xp"); } return $money
这样确实解决了这个并发问题,但又有另外一个问题,如果有多个数据库操作中间一段中断是无法对数据还原的,这里我们需要把事务也加上同时默认加锁。
我们修改一下代码看一下
php $money=100;//数据库查询的用户余额 $buy=intval($_GET['buy']); try { $this->startTrans();//开启事务 if ($money>0&& $money-$buy>0) { sleep(10); $moeny-=$buy; $this->commit(); //提交事务 //写入数据库 } } catch (Exception $exceptione) { $this->rollback();//回滚 } return $money php $buy=intval($_GET['buy']); try { $this->startTrans();//开启事务 $money=100;//数据库查询的用户余额 if ($money>0&& $money-$buy>0) { sleep(10); $moeny-=$buy; $this->commit(); //提交事务 //写入数据库 } } catch (Exception $exceptione) { $this->rollback();//回滚 } return $money
在加了事务的悲观锁后,所有请求到已经开启事务的代码,都会进行阻塞只有提交了事务或者回滚才会处理下一个请求。
然而这样的代码并不能防御并发。这也是很多开发中的问题,确实做了事务加锁,依然没有用。 加事务必须是在查询内加,不然依旧会造成并发问题。 我们在改改把读放入事务锁中。
php $buy=intval($_GET['buy']); try { $this->startTrans();//开启事务 $money=100;//数据库查询的用户余额 if ($money>0&& $money-$buy>0) { sleep(10); $moeny-=$buy; $this->commit(); //提交事务 //写入数据库 } } catch (Exception $exceptione) { $this->rollback();//回滚 } return $money
这样也解决了脏读的问题。
脏读:
(针对未提交数据)如果一个事务中对数据进行了更新,但事务还没有提交,另一个事务可以“看到”该事务没有提交的更新结果,这样造成的问题就是,如果第一个事务回滚,那么,第二个事务在此之前所“看到”的数据就是一笔脏数据。
当然也有更复杂的情况可能框架有多个端。这种二次利用的情况更加难以审计。
在实际审计中我们想要精通一个语言的代码审计我们要做的更难
- 要比产品更懂业务
- 要比测试更懂流程
- 要比开发更懂代码
- 要比架构更懂框架
自此囊括从初级到高级的学习就到此为止了,但我们的学习却不能停止,这也是我个人对php代码审计学习的理解肯定有不合理的地方,不足可以直接提出修改,共勉!