代码审计之各语言相关[ING]

Published: 二 15 三月 2022

In Vuln.

慢慢记,现在是草稿状态,不要看!!!!

Java

准备

工具

漏洞挖掘第一步当然是把环境搭起来,然后安装一些必要的工具:

1.Idea:不会还有人用eclipse吧?!!

2.jclasslib/fernflower/bytecodeviewer/jd-gui:各种反编译工具

3.arthas/btrace:各种插桩工具,动态分析工具

4.javaassist/asm:字节码修改工具

5.jarjarbigs:简单但实用的小工具,将所有class和jar合并成一个jar

由于jar和class不便于搜索,所以可以将其先反编译为java文件,此处使用fernflower,可下载最新版编译,idea也自带此jar包:

git clone https://github.com/fesh0r/fernflower.git  # 下载软件
gradlew.bat  # 安装gradle
gradlew.bat build  # 编译项目,最新版默认使用jdk11,可修改build.gradle里的配置

cd workdir  # 切换到项目目录
mkdir src  # 创建新文件夹存放源码
java -jar fernflower.jar -mpm=15 ./class ./src  # 反编译,单个方法超时时间15秒

注:为了调试与审计方便,建议先将jar解压,再将反编译的java和原始class放在同一个目录,这样能方便审计java并在class上调试。另外可以修改fernflower源码不处理非class文件防止破坏其他文件。

知识

1.Java基础语法

2.Java EE语法,就JSP/Servlet/MVC这些

3.其他高级语法:反射,动态代理(proxy/cglibc),动态代码生成,注解,类加载

注解:类似于注释但在多个阶段可使用,如可创建运行时有效的注解,运行时使用反射获取其值并进行相应动作。

img

语言特性漏洞

sun.net.www.protocol.包下面可以看到所支持的协议:gopher file ftp http https jar mailto netdoc

将库和类文件添加到项目里,如一般项目会把classeslib放入WEB-INF目录下,另外可能需要调试J2EE相关的库,可以将其添加到项目结构里:

img

XXE

XXE 漏洞代码及修复代码整理:https://zhuanlan.zhihu.com/p/265402618

Java XXE Vulnerability:https://docs.ioin.in/writeup/joychou.org/_web_java_xxe_vulnerability_html/index.html

一篇文章读懂Java代码审计之XXE:https://blog.csdn.net/sun1318578251/article/details/104426472

SMTP over XXE − how to send emails using Java's XML parser:https://shiftordie.de/blog/2017/02/18/smtp-over-xxe/

SAXBuilder
SAXParserFactory
SAXReader
SAXTransformerFactory
TransformerFactory
ValidatorSample
XMLReader
Unmarshaller
SchemaFactory
.....

SSRF

SSRF in JAVA:https://docs.ioin.in/writeup/joychou.org/_index_php_web_javassrf_html/index.html

JAVA代码审计之XXE与SSRF:https://xz.aliyun.com/t/2761

模板注入

搜Freemarker和Velocity

模板注入一例

LDAP注入

浅谈LDAP注入攻击:https://www.anquanke.com/post/id/212186

表达式注入

EL/OGNL/SPEL

参考

[0] 浅析 OGNL 的攻防史

[1] 由浅入深SpEL表达式注入漏洞

[2] Bean Stalking: Growing Java beans into RCE

文件包含

动态包含:<jsp:include page="<%=file%>"></jsp:include><c:import url="<%= url%>"></c:import>

命令注入

常见的会使用new ProcessBuilder.commandRuntime.getRuntime.exec,注意下后者执行命令时可能需要转义(原理)。

文件上传

multipartfileUploadgetParts获取文件上传点[1],看文件名能不能穿越或传恶意文件,再看文件内容能不能造成危害。

对于spring可搜索multipartResolver看是否修改了默认解析器,它的StandardMultipartFile(默认)不过滤目录穿越而CommonsMultipartFile会过滤[0]。

参考

[0] Spring MultipartFile 文件上传的潜在威胁 -- JOHNSON (2021)

[1] 任意文件上传漏洞 -- 园长

框架

Web Application

Servlet/JSP

spring

现在遇到的基本都是spring,包括spring,spring mvc, spring boot, spring cloud等,它们有各自的应用场景,关系如下:

理解spring需要懂它的面向切面编程(AOP)/控制反转(IOC)/依赖注入(DI)等概念

现在遇到的一般都是基于Spring框架的,Spring家族里有很多东西,但只需理解它的核心概念:

IOC:控制反转,将对象的生成交由Spring实现,这样方便实现DI和AOP

AOP:面向切面编程,可用于避免面向对象编程时必须的层级关系,作为补充它通过功能来复用代码

DI:依赖注入,可自动根据属性类型,名称等注入所需的实例

img

spring mvc一般了解它的处理流程即可,可见“Spring MVC的介绍与执行流程”:

img

这里重点关注匹配handler部分,它的配置方式特别多,但可以在调试时直接在DispatcherServlet(org/springframework/web/servlet/DispatcherServlet.class)的doDispatch上下断点,输出它的this.handlerMappings即可获取所有的HandlerMapping,这里得到的不一定指仅能通过键获取,很多其他的uri也可能被匹配。

spring在查找handler时,可能会做一些奇怪的操作(如可匹配//会去;a=b等等),或者开发自行添加了路径匹配规则(如修改UrlPathHealper),从而绕过一些限制,这部分前者需要研究Spring的内部,后者就是全局搜索spring看开发者用了它的哪些功能,这些功能有什么影响。

至于拦截器,它分adaptedInterceptorsmappedInterceptors,后者是在返回handler时动态匹配的,可以写脚本批量生成所有,但由于它的配置很简单直接搜源码就可以确定了!

历史漏洞
  1. CVE-2022-22965:利用参数绑定修改Tomcat的日志配置实现写马
  2. CVE-202X-XXXXX: 和Shiro一起造成的各种问题
  3. ...
参考
  1. SpringBoot内置tomcat启动原理
  2. 一些spring源码解读

  3. 设计模式角度解读

Java框架级SSM代码审计思路:https://paper.seebug.org/1075/

struts

struts和struts2有些区别,但都很少见了,在我开始挖Java应用后只见过一次,关于它的基础可看W3school,现在一般也就是学它的那一系列漏洞,主要是ognl表达式的问题,可见如下链接:

参考
  1. 从零开始学java web - struts2 RCE分析
  2. 教程:初识struts2/配置文件详解与结果视图/表单参数自动封装与参数类型自动转换/输入校验/struts2拦截器与自定义拦截器/ognl表达式与ActionContext、ValueStack/Struts2 S2-059 漏洞分析

  3. Struts2-059 远程代码执行漏洞(CVE-2019-0230)分析

Web Services

JAX-RS

Java API for RESTful Web Services是针对RESTful的,编程核心是ProviderResource

1.Resource:用于处理请求,就像handler,会被@Path/@GET等方法修饰...

2.Provider:用于增强功能,类似于插件,有多种提供者,它们都会被@Provider注解并实现如下接口:

ContainerRequestFilter&ContainerResponseFilter: 过滤器
ExceptionMapper: 异常映射注册后会捕获处理对应的异常
MessageBodyReader&MessageBodyWriter: 实现请求响应流与相应对象的转换  @Produces和@Consumes表示匹配对应Mime时生成/解析请求
ContextResolver: 解析上下文提供者

这些提供者和资源会以两种方式被加入应用里:

1.通过注解自动扫描

2.通过编程的方式手动添加到Application.getClasses()/getSingletons()里,这里前者的类实例生命周期为每个请求,而后者是整个应用。

常见的实现有jboss家的resteasy/eclipse家的jersey/Apache下的CXF等。

JAX-WS

Java API for RESTful Web Services是针对传统的SOAP/WSDL的,了解下web service协议,常见的框架有WebService/xfire(cxf)/axis等。

组件

持久化

mybatis

mybatis是半自动化的,需要开发编写SQL语句,此时参数可用#{}${}方式放置,后者直接替换如SQL语句再预编译因此存在SQLi,有三种情况无法用#{},因此易出问题[1]

1.like后无法直接用,like concat('%', #{}, '%')的方式是安全的

2.in后的参数

3.oder by后的参数,可用白名单限制

hibernate

它在直接使用SQL语句,且有拼接时有危险,另外它可能有反序列化问题。

  1. ysoserial Java 反序列化系列第二集 Hibernate1
  2. ysoserial Java 反序列化系列第三集 Hibernate2

H2

H2是一个用Java开发的嵌入式数据库,它本身只是一个类库,可以直接嵌入到应用项目中,所有语句都是在当前jvm里执行的。当存在javac时,它可以执行Java命令[1],堆叠注入(猜测未验证),注意每次修改后函数名也需要修改:

// http://www.h2database.com/html/commands.html?highlight=alias&search=alias#create_alias
CREATE ALIAS DDrrArr AS $$void replace() throws java.io.IOException{new java.io.File("webapps/ROOT/weixin.jsp").renameTo(new java.io.File("webapps/ROOT/weixin.jsp.bak"));java.io.FileWriter writer = new java.io.FileWriter("webapps/ROOT/weixin.jsp");writer.write("<%=123;%>");writer.close();}$$;
CALL DDrrArr();

而不存在编译器时,也可以利用先写Native库再执行(涉及任意文件写)的方式实现RCE[0]

参考

[0] Exploiting H2 Database with native libraries and JNI -- Markus Wulftange (2019)

[1] JavaWeb中的信息泄漏——H2 database -- tkswifty (2021)

Ehcache

。。。

MYSQL-JDBC反序列化入口

使用MySQL JDBC连接器在连接可控时可能存在反序列化入口点(版本小于8.0.23),若再存在利用链则可RCE,原因是它的结果集封装了反序列化操作getObject,如下:

    public Object getObject(int columnIndex) throws SQLException {
        int columnIndexMinusOne = columnIndex - 1;

        Field field = this.fields[columnIndexMinusOne];

        switch (field.getSQLType()) {
            /* 当SQL类型与域类型如下时,将会调用getObjectDeserializingIfNeeded*/
            case Types.BIT:
                if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT && !field.isSingleBit()) {
                    return getObjectDeserializingIfNeeded(columnIndex);
                }
                return Boolean.valueOf(getBoolean(columnIndex));
            ...
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
                if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_GEOMETRY) {
                    return getBytes(columnIndex);
                }
                return getObjectDeserializingIfNeeded(columnIndex);
            ...
        }
    }

    private Object getObjectDeserializingIfNeeded(int columnIndex) throws SQLException {
        final Field field = this.fields[columnIndex - 1];

        if (field.isBinary() || field.isBlob()) {
            byte[] data = getBytes(columnIndex);

            if (this.connection.getAutoDeserialize()) {  // 设置了自动反序列化
                Object obj = data;

                if ((data != null) && (data.length >= 2)) {
                    if ((data[0] == -84) && (data[1] == -19)) {  // 数据以ACED开始
                        try {
                            ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
                            ObjectInputStream objIn = new ObjectInputStream(bytesIn);
                            obj = objIn.readObject();
            ...
    }

如果我们能(1)控制MySQL查询的返回数据且(2)设置了自动反序列化,而且(3)会对结果集调用getObject,就能在此处进行反序列化。这个连接器支持的一个拦截器满足条件(3):

public class ServerStatusDiffInterceptor implements StatementInterceptor {

    public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement, ResultSetInternalMethods originalResultSet, Connection connection)
            throws SQLException {

        if (connection.versionMeetsMinimum(5, 0, 2)) {
            populateMapWithSessionStatusValues(connection, this.postExecuteValues);
        ...
    }
    private void populateMapWithSessionStatusValues(Connection connection, Map<String, String> toPopulate) throws SQLException {

        try {
            stmt = connection.createStatement();
            rs = stmt.executeQuery("SHOW SESSION STATUS");
            Util.resultSetToMap(toPopulate, rs);
            ...
    }

    public static void resultSetToMap(Map mappedValues, java.sql.ResultSet rs) throws SQLException {
        while (rs.next()) {
            mappedValues.put(rs.getObject(1), rs.getObject(2));
        }
    }

漏洞点能指定连接器,连接器能指定拦截器,拦截器会在查询前后自动执行于是满足了条件(3),这个漏洞点又满足条件(1)(2),串起来就是个反序列化点了,接下来就是找利用链,至于如何构造数据包可参考[1][2][3]。

安全

Shiro

密钥不正确或者反序列化后类不正确会返回rememberMe=deleteMe

CVE-2020-11989,CVE-2020-13933权限绕过漏洞

shiro < 1.6.0的认证绕过漏洞分析(CVE-2020-13933):https://www.anquanke.com/post/id/214964

Spring Security

参考

[0] JAVA安全编码与代码审计

[1] java代码审计手书

[2] 技术分享|浅谈Java Web漏洞分析

shell

  1. shell需要过滤的字符有'"|<>()[]~?*{};&$\r\n`\在不需要转义的字符上无效,一个正确的转义方法是将参数用''包裹,并将所有'替换为'\''

  2. 对于空白符可尝试用${IFS},$IFS$9,%09 等方式绕过。

  3. 注意一些命令的参数,如find-exec {} \;-ok,-deleteawksystemprint等都可以执行命令

Python

沙盒逃逸

Python 沙盒绕过禁用import的情况下绕过python沙箱用python继承链搞事情...

反序列化

PyYalm和自身的序列化都可实现任意代码执行,存放于外部的pickle数据也是不安全的,如redis可能存在漏洞数据被改写,可参考Python反序列化漏洞的花式利用Python pickle 反序列化实例分析...

格式化串

Python格式化字符串漏洞(Django为例),Python的format若能控制格式化串则可泄漏任意数据,而f’’型若内容可控则可执行任意代码,不过后者出漏洞的情况应该很少。Python Web之flask session&格式化字符串漏洞。对此的防御当然是不让它可控最好,若实在要可控就上沙盒过滤吧

命令注入

Python内置的代码注入就是system/popen/subprocess/getoutput等方法,一般现代的代码会使用subprocess库,此时就看有没有用shell=True与列表传指令参数,其他若是拼接当然是看过滤是否可以绕过,shlex.quote(),可参考Python Command Execution

代码注入

eval/exec/timeit/execfile/assert/compile/input,一般eval可考虑用ast.literal_eval或json等函数代替

模板注入

模板注入一般会用到格式化串和沙箱逃逸的方法

目录穿越

有个经典的问题就是在目录拼接时,追加的路径以/字符:/开始将丢弃前面的目录,以此作为起始位置继续拼接

XXE

如果是defusedxml就当是很安全吧,毕竟下面这张图就是作者做的,其他的可以库可见下图:

image-20220721191343262

sax包从3.71默认禁用外部实体,或者使用xml_parser.setFeature(feature_external_ges, False)禁用;lxml需要手动XMLParser(resolve_entities=False, no_network=True)禁用;其他如图...

其他还可参考:[0]Pythonanquan,[1]关于Python sec的一些简单的总结

之前还有两个有名的CRLF漏洞urllib header inject(CVE-2016-5699,CVE-2019-9740) 有时也要关注导入的库,特别不知名的库可能就写了漏洞

JS

基础

node可通过添加启动参数--inspect或发送SIGUSR1启用,它默认监听9229端口,远程调试可使用如下命令建立ssh隧道[1]:

ssh -L 9229:localhost:9229 <root>@<192.168.30.204>

对于EGG,它会有多个进程,若存在或可以安装egg-bin时可使用npm run debug命令开启调试[0],否则可以直接在想调试的进程上开调试,如调试工作进程可修改app_worker进程数为1后,直接在它上面开调试。

在多进程时,它会依次递增端口号在子进程上监听。

原型链污染

最出名的是原型链污染,js是基于原型的,在读写属性时若本地没有则会向上查找,可通过这种特性去修改原型链上的属性值,基于该原型的其他实例可能会使用该值,进而造成其他漏洞[2, 3],具体来说如果能出现这种a[x][y][.]*=z语句,我们能控制xyz即可进行污染,但是通常不会出现这种情况,而是出现在递归合并与点分路径解析中,如[5]曾经爆的命令执行漏洞。

当出现原型链污染时,需要根据具体应用分析可行的利用方式,如绕过授权或命令执行,通过AST工具[9]来执行代码....

命令注入

在使用child_process时,有三个命令执行函数,exec直接调sh -c最危险,execFilespawn直接调程序一般没问题,但是如果加了shell=true选项时也有问题[6]。

SQL注入

同,基本都是用参数化查询或者ORM因此出现问题概率是比较小的,node下常见的是node-mysqlSequelize,这里简单记录下监视点的位置:

node_modules/sequelize/lib/dialects/mysql/query.js:run

同,有些语法无法用参数化,如like此时开发人员可能会做一些危险的操作,如用Sequelizeliteral方法,此时如果没有正确的过滤就可能存在安全问题。

记下一些库审计点:

1.pg-promise:是pg库,通常的写法都是由它的格式化函数处理没问题,但若参数直接拼接到语句里,使用:raw/^:value/#时会有问题,前者不会进行任何转义,后者会正确转义但不会对字符串值添加引号。

反序列化

另外它也逃不脱反序列化影响,而且它的反序列化是最容易利用的,既不需要利用链也不需要构造复杂的payload,见[4]。

XXE

一般不会有问题,但是若libxmljs使用了{noent: true}选项则存在问题,sax-js不会处理自定义的DTD因此没问题,而xml2js依赖于sax-js因此也不受影响。

模板注入

一例[10]

其他

Buffer(Num)会存在未清空数据...

参考

[0] Node.js 和 Egg.js 项目远程调试 -- HANK WEI (2020)

[1] NODEJS官网:调试指南

[2] 深入理解 JavaScript Prototype 污染攻击

[3] Prototype pollution attack

[4] 利用 Node.js 反序列化漏洞远程执行代码 -- Ajin Abraham, Holic[译] (2017)

[5] 例: Prototype Pollution in Kibana例: Remote Code Execution via Prototype Pollution in Blitz.js

[6] Command Injection in Node.js -- NF997, gkouziik (2020)

[7] From Markdown to RCE in Atom

[8] Modern Alchemy: Turning XSS into RCE

[9] AST注入 --

[10] The Secret Parameter, LFR, and Potential RCE in NodeJS Apps -- CAPTAINFREAK (2021)

[11] A tale of making internet pollution free -- s1r1us...

Lua

在漏洞挖掘中可能在这三种场景遇到lua:

1.单独出现:作为CGI或完全使用lua实现服务

2.嵌入与C模块:lua作为小巧且高效的语言经常和C混合使用,找luaopen_*它会在require时被自动调用,相关语法可见

3.openresty:作为2的特例,由于其性能很好因此正在被很多厂商采用成功焕发第二春因此需要多研究...

危险函数

  1. 代码执行:loadfiledofileloadstring()()requireloadlib
  2. 命令执行:executepopen

附:还没遇到过,在有沙盒时可参考这里的函数

\0截断 文件系统/自己实现的代码

Perl

perl语言和其他的语法差别还是挺大的,可先看两个半小时学会Perl熟悉下。

危险函数

命令执行

1.system:和其他语言一样,当参数是字符串时用sh -c,当是数组时能安全处理它

2.exec/execvp:和Linux的exec一样不返回

3.` `:同php等语言

4.open:只有两个参数时,文件名的开头或结尾是|表示其他部分为命令并执行,三个参数时第二和第三个参数可控也同理

Golang

千言万语

Go是编译型语言,入口比较稳定,像web也可以直接从main开始跟路由/中间件注册情况...

项目布局

- my-go-project
 - cmd  入口代码如主函数放这里
 - pkg  /外部依赖
 - internal  内部库在编译时会检查约束其内部可以继续命名目录来指定给哪个应用使用
 - go.mod && go.sum  go模块依赖
 - Makefile  编译脚本

编译与调试

远程调试有两种方法,先编译或直接指定项目源码,它们都需要先安装dlv服务端:

# 直接使用delev编译并调试项目
dlv debug --headless --listen=:2345 --api-version=2 --accept-multiclient

# 先编译带符号的项目再调试 
go build -gcflags "all=-N -l" github.com/app/demo  # 1.10及之后的版本
go build -gcflags "-N -l" github.com/app/demo  # 之前的版本
# 调试方式启动也可以使用attach方式附加在运行中的进程上
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./demo

其中为-gcflags编译选项,-N表示禁用优化,-l表示禁用内联。

常见组件

数据库

之前提到如果用了ORM框架一般都是比较安全,但是可能会漏掉一些或者某些点无法预编译,因此这里的检查就是查看所有的语句看是否存在问题...

Gorm

Exec, Raw, Select, Where

Sql

Query, QueryRow, QueryContext, QueryRowContext

Xorm

Query, QueryString, SQL, Where

Etcd

ETCD是一种用于分布式系统的键值型数据库,它主要用于存储配置信息,可使用etcdctl get --prefix ''获取所有的数据

WEB

Gin

序列化

protobuf

在调试接口时可以使用postman,它也支持websocket/grpc请求,不过离线环境不可用(垃圾!),所以替代的grpc可食用bloomrpc

通用问题

XSS

没问题的

如直接输出未进行任何过滤,使用模板...

特性问题

命令注入

exec.Command
exec.CommandContext -> CombinedOutput/Output/Run/Start

testres,err :=exec.Command("sh", "-c", testStr).Output()

**subshell**

文件写

os.OpenFile("./assets/img/"+handler.Filename,os.O_WRONLY|os.O_CREATE,0666) 

模板注入

它自带了text/templatehtml/template,前者容易出现xss后者会编码,除此之外的模板注入比较难利用,它的语法见文档

内存破坏

Go本身是内存安全的,但是为了提供一些烧操作接口,还是提供了两个库:unsafe可进行有限的指针运算,cgo能粘合C代码,因此当使用这两个库时需要仔细检查是否有内存破坏的问题...

未检查错误

当会返回错误状态时需要先检查错误再继续使用返回值,没检查可能会有问题...

整数溢出

Go的整数有长度限制,因此在进行运算或类型转换时可能存在溢出/截断...

自动化工具

  1. gosec: 通过分析Go代码的AST与规则匹配扫描问题(试了下各种报错...)
  2. gokart: 使用SSA做静态分析,支持污点追踪...

参考

  1. https://github.com/OWASP/Go-SCP

Misc

暂时不知道放哪里,就先扔这里吧,在挖掘CGI时为了调试方便可以先替换原文件并且抓包:

#!/bin/bash

echo 'Content-type: text/html'
echo 
# 输出环境变量
env
# 输出body
dd

或:

#!/usr/bin/python

import os
import sys

res += '\n'.join(["export %s='%s'"%(i,j) for i,j in os.environ.items()])
with open('/tmp/post_data', 'wb') as f:
  f.write(sys.stdin.read())
print("Content-type: text/plain")
print("Content-Length: " + str(len(res)) + "\r\n")
print(res)

之后再用直接source导入环境变量并使用<重定向输入即可,此时可用strace,或是gdb:

gdb file
> b *xxx
> run file params < /tmp/post_data