0x05 清楚地知道系统参数过滤情况

0x05.1 原生 GET,POST,REQUEST

使用原生GET,POST,REQUEST变量是完全不过滤的

测试方法:
最简单的就是这样了
顺便找处系统中可外部访问的地方
如图:
PbootCMS漏洞合集之审计全过程之二-了解系统参数与底层过滤情况 - 图1
PbootCMS漏洞合集之审计全过程之二-了解系统参数与底层过滤情况 - 图2
根据结果就可以确定当使用了原生GET,POST,REQUEST变量带入数据库之类,是有可能产生注入,存储型xss之类的

0x05.2 系统外部变量获取函数 get(),post(),request()

路径:PbootCMS-V1.2.1\core\function\helper.php
函数_1:function get(
函数_2:function post(
函数_3:function request(

这3个函数 get(), post(), request()
函数:filter(
最后都会调用 filter函数
这个函数没有用所以直接看 filter 里面调用的方法 escape_string函数进行最终过滤

因为filter无用的内容太多所以我们忽略掉直接看escape_string 函数

路径:PbootCMS-V1.2.1\core\function\handle.php
函数:function escape_string(

  1. // 获取转义数据,支持字符串、数组、对象
  2. function escape_string($string, $dropStr = true)
  3. {
  4. if (! $string)
  5. return $string;
  6. if (is_array($string)) { // 数组处理
  7. foreach ($string as $key => $value) {
  8. $string[$key] = escape_string($value);
  9. }
  10. } elseif (is_object($string)) { // 对象处理
  11. foreach ($string as $key => $value) {
  12. $string->$key = escape_string($value);
  13. }
  14. } else { // 字符串处理
  15. if ($dropStr) {
  16. $string = preg_replace('/(0x7e)|(0x27)|(0x22)|(updatexml)|(extractvalue)|(name_const)|(concat)/i', '', $string);
  17. }
  18. $string = htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
  19. $string = addslashes($string);
  20. }
  21. return $string;
  22. }

可以看得到所有传过来的内容都会先过一个正则匹配过滤
会将 0x7e,0x27,0x22,updatexml,extractvalue,name_const,concat 将其替换为’’

然后在进行
htmlspecialchars 函数的html实体编码
addslashes 函数转义。

  1. 注意点:
  2. 1. 过滤只针对 value
  3. 2. key 完全无不过滤
  4. 3. 那个正则绕过很简单只需要 updatupdatexmlexml 经过过滤以后就可以转为 updatexml

那么看到这里对于系统的初步情况其实就已经很明确了

0x06 查看系统DB类,了解数据库底层运行方式

在挖sql注入的时候,我最喜欢的就是先看这个系统的底层了。
会对挖洞有很大的帮助,看的时候并不需要全看懂,只需要对一些关键的地方看个大概即可

路径:PbootCMS-V1.2.1\core\basic\Model.php

看了一下以后发现,这里没什么好讲的,因为里面全是字符串拼接。

例如: where方法

  1. /**
  2. * 连贯操作:设置查询条件
  3. *
  4. * @param mixed $where
  5. * 设置条件,可以为字符串、数组,
  6. * 字符串模式:如"id<1","name like %1",
  7. * 数组模式:array('username'=>'xie',"realname like '%谢%'")
  8. * @param string $inConnect
  9. * 调用本方法时$where参数数组内部的条件默认使用AND连接
  10. * @param string $outConnect
  11. * 调用本方法时与前面条件使用AND连接
  12. * @param boolean $fuzzy
  13. * 条件是否为模糊匹配,即in匹配
  14. * @return \core\basic\Model
  15. */
  16. final public function where($where, $inConnect = 'AND', $outConnect = 'AND', $fuzzy = false)
  17. {
  18. if (! $where) {
  19. return $this;
  20. }
  21. if (isset($this->sql['where']) && $this->sql['where']) {
  22. $this->sql['where'] .= ' ' . $outConnect . '(';
  23. } else {
  24. $this->sql['where'] = 'WHERE(';
  25. }
  26. if (is_array($where)) {
  27. $where_string = '';
  28. $flag = false;
  29. foreach ($where as $key => $value) {
  30. if ($flag) { // 条件之间内部AND连接
  31. $where_string .= ' ' . $inConnect . ' ';
  32. } else {
  33. $flag = true;
  34. }
  35. if (! is_int($key)) {
  36. if ($fuzzy) {
  37. $where_string .= $key . " like '%" . $value . "%' ";
  38. } else {
  39. $where_string .= $key . "='" . $value . "' ";
  40. }
  41. } else {
  42. $where_string .= $value;
  43. }
  44. }
  45. $this->sql['where'] .= $where_string . ')';
  46. } else {
  47. $this->sql['where'] .= $where . ')';
  48. }
  49. return $this;
  50. }

初步结论:整个db 类的底层都是类似的字符串拼接,所以(≧∇≦)ノ 只要能够带入 '或是\ 那么就可以确定是有注入的了

注意点:在查看的时候发现了 insert 方法的注释,也是需要注意的,这里我把代码贴一下

  1. /**
  2. * 数据插入模型
  3. *
  4. * @param array $data
  5. * 可以为一维或二维数组,
  6. * 一维数组:array('username'=>"xsh",'sex'=>'男'),
  7. * 二维数组:array(
  8. * array('username'=>"xsh",'sex'=>'男'),
  9. * array('username'=>"gmx",'sex'=>'女')
  10. * )
  11. * @param boolean $batch
  12. * 是否启用批量一次插入功能,默认true
  13. * @return boolean|boolean|array
  14. */
  15. final public function insert(array $data = array(), $batch = true)
  16. {
  17. // 未传递数据时,使用data函数插入数据
  18. if (! $data && isset($this->sql['data'])) {
  19. return $this->insert($this->sql['data']);
  20. }
  21. if (is_array($data)) {
  22. if (! $data)
  23. return;
  24. if (count($data) == count($data, 1)) { // 单条数据
  25. $keys = '';
  26. $values = '';
  27. foreach ($data as $key => $value) {
  28. if (! is_numeric($key)) {
  29. $keys .= "`" . $key . "`,";
  30. $values .= "'" . $value . "',";
  31. }
  32. }
  33. if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
  34. $keys .= "`" . $this->createTimeField . "`,`" . $this->updateTimeField . "`,";
  35. if ($this->intTimeFormat) {
  36. $values .= "'" . time() . "','" . time() . "',";
  37. } else {
  38. $values .= "'" . date('Y-m-d H:i:s') . "','" . date('Y-m-d H:i:s') . "',";
  39. }
  40. }
  41. if ($keys) { // 如果插入数据关联字段,则字段以关联数据为准,否则以设置字段为准
  42. $this->sql['field'] = '(' . substr($keys, 0, - 1) . ')';
  43. } elseif (isset($this->sql['field']) && $this->sql['field']) {
  44. $this->sql['field'] = "({$this->sql['field']})";
  45. }
  46. $this->sql['value'] = "(" . substr($values, 0, - 1) . ")";
  47. $sql = $this->buildSql($this->insertSql);
  48. } else { // 多条数据
  49. if ($batch) { // 批量一次性插入
  50. $key_string = '';
  51. $value_string = '';
  52. $flag = false;
  53. foreach ($data as $keys => $value) {
  54. if (! $flag) {
  55. $value_string .= ' SELECT ';
  56. } else {
  57. $value_string .= ' UNION All SELECT ';
  58. }
  59. foreach ($value as $key2 => $value2) {
  60. // 字段获取只执行一次
  61. if (! $flag && ! is_numeric($key2)) {
  62. $key_string .= "`" . $key2 . "`,";
  63. }
  64. $value_string .= "'" . $value2 . "',";
  65. }
  66. $flag = true;
  67. if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
  68. if ($this->intTimeFormat) {
  69. $value_string .= "'" . time() . "','" . time() . "',";
  70. } else {
  71. $value_string .= "'" . date('Y-m-d H:i:s') . "','" . date('Y-m-d H:i:s') . "',";
  72. }
  73. }
  74. $value_string = substr($value_string, 0, - 1);
  75. }
  76. if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
  77. $key_string .= "`" . $this->createTimeField . "`,`" . $this->updateTimeField . "`,";
  78. }
  79. if ($key_string) { // 如果插入数据关联字段,则字段以关联数据为准,否则以设置字段为准
  80. $this->sql['field'] = '(' . substr($key_string, 0, - 1) . ')';
  81. } elseif (isset($this->sql['field']) && $this->sql['field']) {
  82. $this->sql['field'] = "({$this->sql['field']})";
  83. }
  84. $this->sql['value'] = $value_string;
  85. $sql = $this->buildSql($this->insertMultSql);
  86. // 判断SQL语句是否超过数据库设置
  87. if (get_db_type() == 'mysql') {
  88. $max_allowed_packet = $this->getDb()->one('SELECT @@global.max_allowed_packet', 2);
  89. } else {
  90. $max_allowed_packet = 1 * 1024 * 1024; // 其他类型数据库按照1M限制
  91. }
  92. if (strlen($sql) > $max_allowed_packet) { // 如果要插入的数据过大,则转换为一条条插入
  93. return $this->insert($data, false);
  94. }
  95. } else { // 批量一条条插入
  96. foreach ($data as $keys => $value) {
  97. $result = $this->insert($value);
  98. }
  99. return $result;
  100. }
  101. }
  102. } elseif ($this->sql['from']) {
  103. if (isset($this->sql['field']) && $this->sql['field']) { // 表指定字段复制
  104. $this->sql['field'] = "({$this->sql['field']})";
  105. }
  106. $sql = $this->buildSql($this->insertFromSql);
  107. } else {
  108. return;
  109. }
  110. return $this->getDb()->amd($sql);
  111. }

简单的说,就是insert 方法支持二维数组,当使用二维数组时表示批量插入。
所以如果我们可以插入二维数组并且可以控制key那么我们就可以注入了。

下图为触发点查看:
PbootCMS漏洞合集之审计全过程之二-了解系统参数与底层过滤情况 - 图3

0x07 系统情况初步集合

经过0x05.1与0x05.2 还有0x06组合下来我们其实可以确定了一些基本漏洞了

  1. 5.1 反应给我的情况
  2. 首先是xss漏洞
  3. 存储型xss
  4. 如果是使用了 5.1 的外部变量并且入库的时候没有转义的话,那么就会产生xss
  5. 反射xss
  6. 反不动 只要 GET 请求中出现了 A-Z a-z 0-9 之外的数,就会直接报错
  7. 然后是sql注入漏洞
  8. 如果是使用了 5.1 的外部变量并且入库的时候没有转义的话,那么就会产生sql注入
  1. 5.2 反应给我的情况
  2. 首先是xss漏洞
  3. 存储型xss
  4. 如果是使用了 5.2 的外部变量入库的,想找xss 那么就要看使用了 htmlspecialchars_decode 函数的地方,否则的话就只能查看类似这种点 <img src=# {{variate}}> variate 可控的情况
  5. 反射xss
  6. 射不动 只要 GET 请求中出现了 A-Z a-z 0-9 之外的数,就会直接报错
  7. 然后是sql注入漏洞
  8. key 没有过滤所以如果我能够控制 key 进入sql的话,那么就基本上百分之90sql注入了
  1. 6.0 给我的情况
  2. 底层全是字符串拼接,只要能够引入 ' 或是 \ 就可以造成注入
  3. 5.1 只要我们可以控制就有注入
  4. 5.2 因为所有的 value 都会进行 htmlspecialchars 函数的html实体编码 addslashes 函数转义
  5. 所以利用value 注入不现实,那么我们找注入的关键点就是查看key了

嗯,可以看到这就很明了了,我的审计收集工作也正式算是做完了。
接下来就是正式审计了。
不过其实到这一步,基本上在看两眼搜索一下打打 debug 就可以确定漏洞了。