给 Cobra 增加 Node.js 源代码扫描规则

Cobra是一款源代码安全审计工具,支持检测多种开发语言源代码中的大部分显著的安全问题和漏洞。

但是 Cobra 目前基本没有专门针对 Node.js 的源代码扫描规则。以我上一篇文章分析 CVE-2017-16082 时使用的示例漏洞代码为例:

const app = new Koa()
const client = new Client({
    user: "f1sh",
    password: "f1sh",
    database: "postgres",
    host: "127.0.0.1",
    port: 5432
})
client.connect()

app.use(async ctx => {
    ctx.response.type = 'html'

    let id = ctx.request.query.id || 1
    let sql = `SELECT * FROM "user" WHERE "id" = ${id}`
    const res = await client.query(sql)

    ctx.body = `<html>
                    <body>
                        <table>
                            <tr><th>id</th><td>${res.rows[0].id}</td></tr>
                            <tr><th>name</th><td>${res.rows[0].name}</td></tr>
                            <tr><th>score</th><td>${res.rows[0].score}</td></tr>
                        </table>
                    </body>
                </html>`
})

app.listen(3000)

这个代码中存在着明显的 SQL 语句拼接,有 SQL 注入风险,但是使用 Cobra 扫描会得到:

查看 Cobra 的 rules 文件夹下的扫描规则也可以发现,当前的 Cobra 只支持扫描 java 和 php 中的 SQL 注入漏洞,而没有 Node.js 的对应规则。

那么我们何不自己动手添加呢?编写 Cobra 的扫描规则并不难,在官方文档中给了非常详细的说明和示例。

根据文档的说明和参考当前存在的规则,编写一个扫描 Node.js 中拼接 SQL 语句的规则如下:

<?xml version="1.0" encoding="UTF-8"?>

<cobra document="https://github.com/WhaleShark-Team/cobra">
    <name value="拼接SQL注入"/>
    <language value="javascript"/>
    <match mode="regex-only-match"><![CDATA[(['"`]\s*(select|SELECT|insert|INSERT|update|UPDATE|delete|DETELE)[^\w.]+[^;]*?(\$\{(.+?)\}|['"`]\s*\+\s*[$a-zA-Z_][$\w]?))]]></match>
    <level value="8"/>
    <test>
        <case assert="true"><![CDATA[let sql = `SELECT * FROM "user" WHERE "id" = ${id}`]]></case>
        <case assert="false"><![CDATA[let sql = 'SELECT * FROM "user" WHERE "id" = 1']]></case>
        <case assert="true"><![CDATA[let sql = 'SELECT * FROM "user" WHERE "id" = ' + id]]></case>
        <case assert="true"><![CDATA[const result = await req.db.run(`UPDATE notes SET pinned = ${req.body.value}::boolean WHERE id = ${req.params.noteId}`);]]></case>
    </test>
    <solution>
        ## 安全风险
        拼接SQL语句存在SQL注入风险

        ## 修复方案
        - 已不再使用的功能,直接做下线处理
        - 引入安全组件
        - 代码层修复:尽量不要拼接SQL语句。如必要,需对用户可控的参数进行严格过滤
    </solution>
    <status value="on"/>
    <author name="f1sh" email="f1shunicorns@gmail.com"/>
</cobra>

规则的核心就是 <match> 标签中的正则表达式。

这个正则表达式分成三个部分,第一个部分

['"`]\s*(select|SELECT|insert|INSERT|update|UPDATE|delete|DETELE)

匹配的是 SQL 语句开头的关键字;第二个部分

[^\w.]+[^;]*?

匹配的是 SQL 语句结束前的部分;第三个部分

(\$\{(.+?)\}|['"`]\s*\+\s*[$a-zA-Z_][$\w]?)

我匹配了 js 中两种字符串拼接的情况:

let sql = `SELECT * FROM "user" WHERE "id" = ${id}`
let sql = 'SELECT * FROM "user" WHERE "id" = ' + id

另外这里需要注意的地方是 Cobra 并没有使用 python 的 re 库来解析<match> 中的正则表达式,而是使用的 grep 命令,并且没有使用 -i 参数,所以关键字的大小写敏感,得把关键字大小写的情况都写进去(或者手动给 grep 加上 -i 参数,在 cobra/engine.py 第317行)。

保存规则为 CVI-160005.xml ,再一次扫描之前的示例代码得:

这样就成功地给 Cobra 加上了 Node.js 源代码 SQL 注入风险扫描的规则。

我们还知道 CVE-2017-16082 这个漏洞的成因其实是传入 Function 这个类的最后一个参数可控,造成了代码注入,那么我们还可以写个规则来扫描 Function 代码注入风险:

<?xml version="1.0" encoding="UTF-8"?>

<cobra document="https://github.com/WhaleShark-Team/cobra">
    <name value="远程代码执行"/>
    <language value="javascript"/>
    <match mode="regex-only-match"><![CDATA[([^\w]Function\(.*?([$\w]+|(['"`].*?(\$\{(.+?)\}|['"`]\s*\+\s*[$\w]+).*?))\s*\))]]></match>
    <level value="10"/>
    <test>
        <case assert="true"><![CDATA[this.RowCtor = Function('parsers', 'rowData', ctorBody)]]></case>
        <case assert="true"><![CDATA[this.RowCtor = Function('parsers', 'rowData', 'var ' + ctorBody + ';')]]></case>
        <case assert="true"><![CDATA[this.RowCtor = Function('parsers', 'rowData', `var ${ctorBody};`)]]></case>
        <case assert="false"><![CDATA[var sum = new Function('a', 'b', 'return a + b');]]></case>
    </test>
    <solution>
        ## 安全风险
        代码注入(Code Injection) - Funtion()
        没有对用户输入的参数进行足够的过滤,可能导致攻击者传入的恶意代码执行。
        因此危险函数应该尽量避免在开发中使用。

        ## 修复方案
        不要把用户可控的参数带入 Function 的最后一个参数中,或者进行安全的过滤后再带入。
    </solution>
    <status value="on"/>
    <author name="f1sh" email="f1shunicorns@gmail.com"/>
</cobra>

这个规则匹配了 Function 类最后一个参数可控的三种情况:

this.RowCtor = Function('parsers', 'rowData', ctorBody)
this.RowCtor = Function('parsers', 'rowData', 'var ' + ctorBody + ';')
this.RowCtor = Function('parsers', 'rowData', `var ${ctorBody};`)

规则写好后,保存为 CVI-180002.xml ,再次扫描。但是这时会发现并没有扫描出 Function 代码注入风险,为什么呢?

我们运行 Cobra 时加上 -d 参数可以查看扫描时的 debug 信息:

可以看到其实是成功扫描出了 result.js 中存在漏洞的那一行代码了的,但是输出了:

[13:37:39] [DEBUG] Not vulnerability: Special File(特殊文件)

也就是说被当成了特殊文件,所以没有算作漏洞。

怎么样才会被当成特殊文件呢,在 Cobra 的源代码目录里全局搜索一下关键字 Special File

跟进 cobra/engine.py 第588行:

if self.is_special_file():
    logger.debug("[RET] Special File")
    return False, 'Special File(特殊文件)'

跟进 is_special_file() 函数,在同文件 493 行:

def is_special_file(self):
    """
    Is special file
    :method: According to the file name to determine whether the special file
    :return: boolean
    """
    special_paths = [
        '/node_modules/',
        '/bower_components/',
        '.min.js',
        '.log',
        '.log.',
        'nohup.out',
    ]
    for path in special_paths:
        if path in self.file_path:
            return True
    return False

可以看到在这个函数中维护了一个 special_paths ,当文件存在于 special_paths 中时就不会被当作漏洞。而我们扫描出存在漏洞的 result.js 正存在于 /node_modules/ 目录下,所以就没有被当作漏洞了。我们尝试把 /node_modules/special_paths 注释掉,然后再次扫描:

就可以正常扫描出漏洞风险了。

以这两条规则为例,我们就可以很轻易的写出一份针对 Node.js 各种漏洞风险的扫描规则库。

当然这两条规则还非常简陋,仅仅考虑了变量的简单的拼接和传入,并没有去检测拼接或传入进来的变量是否可控或经过了过滤。但 Cobra 最强大的一点就是:

对于OWASP Top 10的漏洞,Cobra通过预先梳理能造成危害的函数,并定位代码中所有出现该危害函数的地方,继而基于Lex(Lexical Analyzer Generator, 词法分析生成器)和Yacc(Yet Another Compiler-Compiler, 编译器代码生成器)将对应源代码解析为AST(Abstract Syntax Tree, 抽象语法树),分析危害函数的入参是否可控来判断是否存在漏洞(目前仅接入了PHP-AST,其它语言AST接入中)

我们可以参考 Cobra 的 PHP-AST ,给 Node.js 也加上 AST ,这样就可以更准确更灵活地扫描源代码中的漏洞。(挖坑待填)

发表评论