前言
本次主要还是想学习一下sql注入的一些姿势点,造成sql注入的代码原理,所以主要是审计的注入。望大师傅们给点建议。
熟悉cms
直接先进入admin.php和index.php
发现index.php有点难以理解。但是大致可以通过函数名和语义分析出是根据一些变量或者一些路径来渲染,加载模板文件,随后回显到前端。
再看admin.php,发现存在action参数和ctrl参数。
发现有两个方法,class_exists和method_exists,这两个函数是判断是否存在类和方法的,接下if内的语句判断,指导action是类名,ctrl是函数名,有点像路由
题目搜索发现处理数据库请求的类为cms方法为lists
直接搜索跟进
找到执行sql语句的函数,跟进发现有个存放sql方法的文件
<?php
class Dbclass {
public $conn=NULL;
public $querynum = 0;
static private $_single =null;
function __construct($dbhost,$pconnect = 0){
$this->connect($dbhost,$pconnect = 0);
}
function connect($dbhost,$pconnect = 0){
if(isset(self::$_single[$dbhost])&&$mysqli->ping){
return true;
}
if(RUNONSAE){
$dbhost=SAE_MYSQL_HOST_M.':'.SAE_MYSQL_PORT;
$dbuser=SAE_MYSQL_USER;
$dbpw=SAE_MYSQL_PASS;
$dbname=SAE_MYSQL_DB;
}elseif(RUNONBAE){
$dbconfig=explode('|',$dbhost);
$dbhost = getenv('HTTP_BAE_ENV_ADDR_SQL_IP').':'.getenv('HTTP_BAE_ENV_ADDR_SQL_PORT');
$dbuser = getenv('HTTP_BAE_ENV_AK');
$dbpw = getenv('HTTP_BAE_ENV_SK');
$dbname=$dbconfig[4];
}
else{
$dbconfig=explode('|',$dbhost);
if(count($dbconfig)<4)Base::showmessage( 'taoCMS未被正确安装或配置导致无法读取数据'.$this->error().$this->errno() , WEBURL . 'install.php' );
$dbhost=$dbconfig[1];
$dbuser=$dbconfig[2];
$dbpw=$dbconfig[3];
$dbname=$dbconfig[4];
}
if($pconnect){
if(!$this->conn = new mysqli($dbhost,$dbuser,$dbpw)){
$this->halt();
}
} else {
if(!$this->conn = new mysqli($dbhost,$dbuser,$dbpw)){
$this->halt();
}
}
$this->select_db($dbname);
$this->query('set names utf8');
self::$_single[$dbhost]=true;
}
function select_db($dbname){
return $this->conn->select_db($dbname);
}
function query($sql){
//echo $sql;
$query = $this->conn->query($sql);
return $query;
}
function fetch_array($query,$result_type = MYSQLI_ASSOC){
return $query->fetch_array($result_type);
}
function getlist($table,$wheres = "1=1", $colums = '*',$limits = '20',$orderbys="id DESC"){
$query = $this->query("select ".$colums." from ".$table." where ".$wheres." ORDER BY ".$orderbys." limit ".$limits);
while($rs = $this->fetch_array($query)){
$datas[]=Base::magic2word($rs);
}
return $datas ;
}
function getquery($sqltext){
$sqlArray=array();
$sqlArray=explode('|',$sqltext);
$table=$sqlArray[0];
if(!$sqlArray[0]){
return NULL;
}
$wheres=$sqlArray[1]?$sqlArray[1]:'1=1';
$limits=$sqlArray[2]?$sqlArray[2]:'10';
$orderbys=$sqlArray[3]?$sqlArray[3]:"id DESC";
$colums=$sqlArray[4]?$sqlArray[4]:"*";
$query = $this->query("select ".$colums." from ".$table." where ".$wheres." ORDER BY ".$orderbys." limit ".$limits);
return $query;
}
function add_one($table,$data ){
if (is_array($data)){
foreach ($data as $k=>$v){
$colums.=Base::safeword($k).',';
$columsData.="'".Base::safeword($v)."',";
}
$sql="INSERT INTO ".$table." (".substr($colums,0,-1).") VALUES(".substr($columsData,0,-1).")";
$query = $this->query($sql);
return $this->insert_id();
}
return FALSE;
}
function delist($table,$idArray,$wheres=""){
if($wheres==''){
$ids=implode(',',$idArray);
echo "idarray:".$ids."<br>";
echo "DELETE FROM ".$table." WHERE id in(".$ids.")";
$query = $this->query("DELETE FROM ".$table." WHERE id in(".$ids.")");
}else{
$query = $this->query("DELETE FROM ".$table." WHERE ".$wheres);
}
return $query;
}
function updatelist($table,$data,$idArray){
if (is_array($data)){
foreach ($data as $k=>$v){
$updateData.=Base::safeword($k)."='".Base::safeword($v)."',";
}
$data=substr($updateData,0,-1);
}
$idArray=(array)$idArray;
$ids=implode(',',$idArray);
$query = $this->query("UPDATE ".$table." set ".$data." WHERE id in(".$ids.")");
return $query;
}
function get_one($table,$wheres = "1=1", $colums = '*',$limits = '1',$orderbys="id DESC"){
$sql="select ".$colums." from ".$table." where ".$wheres." ORDER BY ".$orderbys." limit ".$limits;
echo $sql;
$query = $this->query($sql);
if(empty($query)){
return false;
}
$rs = Base::magic2word($this->fetch_array($query));
$this->free_result($query);
return $rs ;
}
function affected_rows(){
return $this->conn->affected_rows;
}
function error(){
return $this->conn->error;
}
function errno(){
return $this->conn->errno;
}
function result($query,$row){
$query->data_seek($row); $query = $query->fetch_array()[0];
return $query;
}
function num_rows($query){
$query = $query->num_rows;
return $query;
}
function num_fields($query){
return $query->field_count;
}
function free_result($query){
return $query->free_result();
}
function insert_id(){
$id = $this->conn->insert_id;
return $id;
}
function fetch_row($query){
$query = $query->fetch_row();
return $query;
}
function halt(){
if(in_array($this->errno(),array(1049,1146,2002,1046))){
Base::showmessage( 'taoCMS未被正确安装或配置导致无法读取数据'.$this->error().$this->errno() , WEBURL . 'install.php' );
}
echo $this->error() . ':' . $this->errno();
}
function close(){
mysqli_close($this->conn);
}
function __destruct(){
$this->close();
}
}
?>
阅读代码,发现有过滤函数对变量进行了一些过滤处理。
发现对输入做了处理,不过只有等级为6的时候才会这样处理,对敏感的字符进行处理,如替换,进制转换等等。
注入点存在处
最后找到几个未过滤的函数方法:delist、getquery、updatelist、get_one、getlist
那就值针对这几个方法看
审计开始
先看第一个函数delist,看到有三个文件有这三个函数,先看第一个
delist
Article类
看到传入的参数有表名、参数id,以及where参数,用于筛选匹配数据
在后台管理系统中没看到该模块的调用,然后看CMS类的时候发现CMS继承了Article类,所以看CMS类就好了
Category
这里可以看到仍然没有对id进行过滤,直接使用sleep(5)延时,看到burpsuite返回的时间是5s,符合执行语句,这次只有一次数据库操作,所以返回时间没啥变化
CMS
跟Article类是一样的语句所以其实是通杀
测试bool盲注,对语句进行拼接,看参数知道是id
payload:27) or 1=1#
发现回显没啥特征进行,采用延时确认,发现成功延时12s,我们的语句写的是4s,说明经历了三次注入,后续再研究
这里payload使用--+是失败的,还是需要用#号,不是很明白
直接上sqlmap,注入出当前user,使用的一些参数
-v 3 --level 5 --risk 3 --random-agent --current-user --technique T --dbms mysql -p id
继续跟进代码,看看为什么产生了三次延时,看看createtag方法
可以看到传参值也是id,而id在加入sql语句前也没有进行安全处理,只针对tags参数进行过滤,但是我们这个删除执行很明显没有传递tag参数,所以走的是下面的else语句,成功拼接到语句中,根据之前阅读的方法知道,delist、getquery、updatelist、get_one、getlist这几个函数中没有对输入值进行过滤,执行我们的payload。
这里有两条语句都拼接了所以一共延时了3次
getquery
可以看到该方法没有合适的调用,都是在一些模板文件中用于加载数据,所以直接放弃
updatelist
category
看到这里的调用,发现是经过这个add_one处理过的,不是传参的那个status
add_one处理POST传输的数据,对数据了过滤转义,然后返回值,所以不存在注入
而在update函数中,没有进行数据数据过滤处理,有可能存在注入
payload:1) or sleep(4)#
这个语句同样也有
getlist
category
根据参数知道id对应的参数为$where参数,对应的同样没有过滤,直接打入payload
延时4s
Admin
全局搜索getlis,在admin.php中找到edit方法存在getlist的调用,并且能够可控参数
那么直接抓包修改id值,注意不能用or,我用or这个payload打的时候没触发sleep()函数,因为or是代表或的意思,而这里id=2,2是存在的,所以就不执行sleep函数,就像命令中的“||”符号。所以用and直接一起执行。
payload:2 and sleep(5)%23
执行成功,直接延时5s
Category
可以看到也是继承的Article,注入位置也是相同的
payload:1 or sleep(5)%23
Cms
筛选到cms类中的updateurl方法存在该函数调用,分析前后发现$addsql参数是由$id参数组合而成的,那么也很明显的存在注入,id没有经过处理。
延时成功
lists方法也存在注入点,继续发现getlist语句的参数由$addsql控制,而该参数能够拼接,发现name参数被用安全方法过滤了危险字符,所以主要看cat和status参数。,在save方法。
这个方法我一直想知道在哪个地方调用,我是用ctrl调用也不行,然后发现他是通过传参调用的,在save方法找到该方法的调用。
读源码的时候发现这个tags参数进行了safeword过滤,但是等级只有3级,没有用最高级的,所以没有对输入做到完全过滤的方法,tags还是能进行注入
根据语句:$tagdata=$this->db->getlist(TB."relations","name='".$tag."'","id,counts",1);可以知道是字符型注入,需要闭合单引号。
payload:test'+or+sleep(2)%23
这个位置也有一个注入点
总结
通过这次的cms审计学习,发现一些通用的模块方法非常需要注意漏洞的产生,如果这些模块方法中有漏洞点,那么很多的方法、功能模块都会出现漏洞,扩大危害