Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⚰️ refactor: Remove commented-out code, unneeded files
Browse files Browse the repository at this point in the history
eliasgierlinger committed Nov 20, 2023
1 parent 083c90e commit fb332cc
Showing 41 changed files with 189 additions and 364 deletions.
7 changes: 7 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 0 additions & 22 deletions .idea/php.xml

This file was deleted.

1 change: 0 additions & 1 deletion .idea/unguard.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ Unguard is composed of eight microservices written in different languages that t
| [proxy-service](./src/proxy-service) | Java Spring | unguard-proxy | Serves REST API for proxying requests from frontend (vulnerable to SSRF; no sanitization on the entered URL). |
| [profile-service](./src/profile-service) | Java Spring | default | Serves REST API for updating biography information in a H2 database; vulnerable to SQL injection attacks |
| [membership-service](./src/membership-service) | .NET 7 | default | Serves REST API for updating user memberships in a MariaDB; vulnerable to SQL injection attacks |
| [like-service](./src/like-service) | PHP | default | Serves REST API for liking and unliking posts using MariaDB; vulnerable to an SQL injection attack for removing other users' likes |
| [like-service](./src/like-service) | PHP | default | Serves REST API for adding likes to posts using MariaDB; vulnerable to SQL injection attacks |
| [user-auth-service](./src/user-auth-service) | Node.js Express | default | Serves REST API for authenticating users with JWT tokens (vulnerable to JWT key confusion). |
| [status-service](./src/status-service) | Go | unguard-status | Serves REST API for Kubernetes deployments health, as well as a user and user role list (vulnerable to SQL injection) |
| jaeger | | default | The [Jaeger](https://www.jaegertracing.io/) stack for distributed tracing. |
7 changes: 0 additions & 7 deletions chart/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -51,11 +51,4 @@ spec:
name: unguard-envoy-proxy
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: unguard-envoy-proxy
port:
number: 8000
{{end}}
3 changes: 1 addition & 2 deletions chart/values.yaml
Original file line number Diff line number Diff line change
@@ -404,7 +404,7 @@ likeService:
ports:
containerPort: 8000
env:
JAEGER_COLLECTOR_HOST: collector # PHP OpenTelemetry sends data to jaeger-collector instead of jaeger-agent
JAEGER_COLLECTOR_HOST: collector # PHP OpenTelemetry sends data to jaeger-collector instead of jaeger-agent
JAEGER_DISABLED: true
JAEGER_PORT: 4318
SERVICE_NAME: unguard-like-service
@@ -455,4 +455,3 @@ frontend:
LIKE_SERVICE_BASE_PATH: /like-service
MEMBERSHIP_SERVICE_BASE_PATH: /membership-service
STATUS_SERVICE_BASE_PATH: /status-service

4 changes: 2 additions & 2 deletions docs/TRACING.md
Original file line number Diff line number Diff line change
@@ -30,13 +30,13 @@ This document explains how to install Jaeger tracing using Helm to the cluster.
1. For local development
1. Install Jaeger (takes a couple of minutes)
```sh
helm install jaeger jaegertracing/jaeger --version 0.71.14 --wait --namespace unguard --create-namespace --values ./chart/jaeger-otlp-values.yaml
helm install jaeger jaegertracing/jaeger --version 0.71.14 --wait --namespace unguard --create-namespace --values ./docs/jaeger/jaeger-otlp-values.yaml
```
2. Install the Jaeger-Operator
```sh
helm install jaeger-operator jaegertracing/jaeger-operator --version 2.22.0 --wait --namespace unguard --create-namespace
```
2. Deploy the AllInOne image for local development
3. Deploy the AllInOne image for local development
```sh
kubectl apply -f ./k8s-manifests/jaeger/jaeger.yaml
```
Binary file modified docs/images/unguard-timeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/unguard-user-profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
2 changes: 1 addition & 1 deletion exploit-toolkit/exploit.py
Original file line number Diff line number Diff line change
@@ -378,7 +378,7 @@ def sql_inject_unlike_post(post, user, target):
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)
r = session.get(f'http://{target + frontend_base_path}/unlike', params={'postId': [post, user]}, allow_redirects=False)

# should always be status code 404
click.echo('Request returned status code %s.' % str(r.status_code))
2 changes: 1 addition & 1 deletion exploit-toolkit/exploits/sql-injection/README.md
Original file line number Diff line number Diff line change
@@ -3,4 +3,4 @@
Unguard has three SQL injection vulnerabilities:
* [One in the Java `profile-service`](./SQLI-PROFILE-SERVICE-H2.md), which is exploitable through the user biography and allows you to access the h2 database.
* [One in the Golang `status-service`](./SQLI-STATUS-SERVICE-MARIADB.md), which is exploitable through the search bar on the Users page and allows you to access the MariaDB database.
* [One in the PHP `like-service`](./SQLI-LIKE-SERVICE-REMOVE-LIKE.md), which allows you to remove another user's like on a given post if you send the right parameters.
* [One in the PHP `like-service`](./SQLI-LIKE-SERVICE-REMOVE-LIKE.md), which allows you to remove another user's like on a given post.
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@

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)).

@@ -20,9 +19,9 @@ This ID is exposed indirectly through the Users page. The admanager user always
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`.
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/1.
You can get to that page by liking the post yourself. From the address bar, you can now see the post id (1 in the example). Then open the following in your browser:
`http://unguard.kube/ui/unlike?postId=[POST_ID]&postId=[USER_ID]`.
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.

16 changes: 0 additions & 16 deletions src/envoy-proxy/config/envoy-config.yaml
Original file line number Diff line number Diff line change
@@ -62,10 +62,6 @@ static_resources:
prefix: /ad-service
route:
cluster: ad_service_cluster
- match:
prefix: /like-service
route:
cluster: like_service_cluster
clusters:
- name: frontend_cluster
dns_lookup_family: V4_ONLY
@@ -91,15 +87,3 @@ static_resources:
socket_address:
address: unguard-ad-service
port_value: 80
- name: like_service_cluster
dns_lookup_family: V4_ONLY
type: STRICT_DNS
load_assignment:
cluster_name: like_service_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: unguard-like-service
port_value: 80
208 changes: 97 additions & 111 deletions src/frontend/site.js
Original file line number Diff line number Diff line change
@@ -38,7 +38,11 @@ router.post('/user/:username/follow', followUser);
// Create post
router.post('/post', createPost);
// get single post
router.get('/post', getPost);
router.get('/post/:postId', getPost);
// Like post
router.get('/like', likePost);
// Unlike post
router.get('/unlike', unlikePost);
// Logout
router.post('/logout', doLogout);
// Login
@@ -53,29 +57,26 @@ router.post('/membership/:username', postMembership);

router.use('/ad-manager', adManagerRouter);

async function showGlobalTimeline(req, res) {
try {
let [timeline, membership] = await fetchUsingDeploymentBase(req, () =>
Promise.all([
req.MICROBLOG_API.get('/timeline'),
getMembershipOfLoggedInUser(req)
]))
let postArray = timeline.data;
postArray = await insertLikeCountIntoPostArray(req, postArray);

let data = extendRenderData({
data: postArray,
title: 'Timeline',
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
baseData: baseRequestFactory.baseData,
membership: membership.data.membership

}, req);
res.render('index.njk', data)
} catch (err) {
displayError(err, res)
}
function showGlobalTimeline(req, res) {
fetchUsingDeploymentBase(req, () =>
Promise.all([
req.MICROBLOG_API.get('/timeline'),
getMembershipOfLoggedInUser(req)
])).
then(([timeline, membership]) => {
insertLikeCountIntoPostArray(req, timeline.data).then(postArray => {
let data = extendRenderData({
data: postArray,
title: 'Timeline',
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
baseData: baseRequestFactory.baseData,
membership: membership.data.membership

}, req);
res.render('index.njk', data)
}, (err) => displayError(err, res))
}, (err) => displayError(err, res))
}

function showUsers(req, res) {
@@ -115,29 +116,25 @@ function showUsers(req, res) {
}, (err) => displayError(err, 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;
postArray = await insertLikeCountIntoPostArray(req, postArray);

let data = extendRenderData({
data: postArray,
title: 'My Timeline',
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
baseData: baseRequestFactory.baseData,
membership: membership.data.membership
}, req);
res.render('index.njk', data);
} catch (err) {
displayError(err, res)
}
function showPersonalTimeline(req, res) {
fetchUsingDeploymentBase(req, () =>
Promise.all([
req.MICROBLOG_API.get('/mytimeline'),
getMembershipOfLoggedInUser(req)
]))
.then(([myTimeline, membership]) => {
insertLikeCountIntoPostArray(req, myTimeline.data).then(postArray => {
let data = extendRenderData({
data: postArray,
title: 'My Timeline',
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
baseData: baseRequestFactory.baseData,
membership: membership.data.membership
}, req);
res.render('index.njk', data);
}, (err) => displayError(err, res))
}, (err) => displayError(err, res))
}

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

let data = extendRenderData({
data: postArray,
profileName: username,
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
bio: bioText,
baseData: baseRequestFactory.baseData,
membership: membership.data.membership
}, req);

res.render('profile.njk', data);
).then(([bioText, microblogServiceResponse, membership]) => {
insertLikeCountIntoPostArray(req, microblogServiceResponse.data).then(postArray => {
let data = extendRenderData({
data: postArray,
profileName: username,
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
bio: bioText,
baseData: baseRequestFactory.baseData,
membership: membership.data.membership
}, req);

res.render('profile.njk', data);
}, (err) => displayError(err, res))
}, (err) => displayError(err, res));
}

@@ -318,7 +314,7 @@ function createPost(req, res) {
imageUrl: metaImgSrc
}))
}, (err) => displayError(err, res))
.then((postResponse) => res.redirect(extendURL(`/post?postId=${postResponse.data.postId}`)), (err) => displayError(err, res));
.then((postResponse) => res.redirect(extendURL(`/post/${postResponse.data.postId}`)), (err) => displayError(err, res));
} else if (req.body.imgurl) {
// the image post calls a different endpoint that has a different ssrf vulnerability
fetchUsingDeploymentBase(req, () => req.PROXY.get("/image", {
@@ -331,48 +327,49 @@ function createPost(req, res) {
imageUrl: response.data
}));
}, (err) => displayError(err, res))
.then((postResponse) => res.redirect(extendURL(`/post?postId=${postResponse.data.postId}`)), (err) => displayError(err, res));
.then((postResponse) => res.redirect(extendURL(`/post/${postResponse.data.postId}`)), (err) => displayError(err, res));
} else if (req.body.message) {
// this is a normal message
fetchUsingDeploymentBase(req, () => req.MICROBLOG_API.post('/post', {
content: req.body.message
})).then((postResponse) => {
res.redirect(extendURL(`/post?postId=${postResponse.data.postId}`));
res.redirect(extendURL(`/post/${postResponse.data.postId}`));
}, (err) => displayError(err, res));
} else {
// when nothing is set, just redirect back
res.redirect(extendURL('/'));
}
}

async function getPost(req, res) {
const postId = req.query.postId;
try {
if(req.query.like_post !== undefined) {
await fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.post(`/like-service/like-post`, {postId: postId}))
}
else if (req.query.like_delete !== undefined) {
await fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.post(`/like-service/like-delete`, {postId: postId}));
}
} catch {}


const likeData = await getLikeCount(req, postId)

fetchUsingDeploymentBase(req, () => req.MICROBLOG_API.get(`/post/${postId}`)).then((response) => {//
function getPost(req, res) {
const postId = req.params.postId;
fetchUsingDeploymentBase(req, () => req.MICROBLOG_API.get(`/post/${postId}`)).then((response) => {
insertLikeCountIntoPostArray(req, [response.data]).then(postArray => {
let postData = postArray[0];
let data = extendRenderData({
post: postData,
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
baseData: baseRequestFactory.baseData
}, req);

let postData = response.data;
postData = {...postData, likeCount: likeData.likeCount, userLiked: likeData.userLiked};
res.render('singlepost.njk', data);
}, (err) => displayError(err, res))
}, (err) => displayError(err, res))
}

let data = extendRenderData({
post: postData,
username: getJwtUser(req.cookies),
isAdManager: hasJwtRole(req.cookies, roles.AD_MANAGER),
baseData: baseRequestFactory.baseData
}, req);
function likePost(req, res) {
const postId = req.query.postId;
fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.post(`/like/` + postId)).then((response) => {
res.redirect(extendURL(`/post/${postId}`));
}, (error) => res.status(statusCodeForError(error)).render('error.njk', handleError(error)));
}

res.render('singlepost.njk', data);
}, (err) => displayError(err, res))
function unlikePost(req, res) {
const postId = req.query.postId;
fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.delete(`/like`, {params: {postId: postId}})).then((response) => {
res.redirect(extendURL(`/post/${postId}`));
}, (error) => res.status(statusCodeForError(error)).render('error.njk', handleError(error)));
}

function postMembership(req, res) {
@@ -394,29 +391,18 @@ function postBio(req, res) {
.then((_) => {
res.redirect(extendURL(`/user/${getJwtUser(req.cookies)}`));
}).catch(error => {
res.status(statusCodeForError(error)).render('error.njk', handleError(error));
});
}


async function getLikeCount(req, postId) {
let response = await fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.get(`/like-service/like-count/` + postId))
return response.data
}

async function getMultipleLikeCounts(req, postIds) {
let response = await fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.get(`/like-service/like-count/`, { params: { postIds: postIds } }))
return response.data
res.status(statusCodeForError(error)).render('error.njk', handleError(error));
});
}

async function insertLikeCountIntoPostArray(req, data) {
let likeData = await getMultipleLikeCounts(req, data.map(post => post.postId));

return data.map(post => {
let likeCount = likeData.likeCounts.find(likeCount => likeCount.postId == post.postId)?.likeCount ?? 0;
let userLiked = likeData.likedPosts.some(like => like.postId == post.postId);
return {...post, likeCount: likeCount, userLiked: userLiked};
});
function insertLikeCountIntoPostArray(req, posts) {
return fetchUsingDeploymentBase(req, () => req.LIKE_SERVICE_API.get(`/like`, { params: { postId: posts.map(post => post.postId) } }))
.then(likeResponse => likeResponse.data)
.then(likeData => posts.map(post => {
let likeCount = likeData.likeCounts.find(likeCount => likeCount.postId == post.postId)?.likeCount ?? 0;
let userLiked = likeData.likedPosts.some(like => like.postId == post.postId);
return {...post, likeCount: likeCount, userLiked: userLiked};
}));
}


Binary file removed src/frontend/static/img/thumb_up.png
Binary file not shown.
Binary file removed src/frontend/static/img/thumb_up_liked.png
Binary file not shown.
3 changes: 1 addition & 2 deletions src/frontend/views/base.njk
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ limitations under the License.
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Unguard - {{ title }}</title>
<link rel="stylesheet" href="{{ extendURL('/style.css') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">

<link rel="apple-touch-icon" sizes="57x57" href="{{ extendURL('/apple-icon-57x57.png')}}">
<link rel="apple-touch-icon" sizes="60x60" href="{{ extendURL('/apple-icon-60x60.png')}}">
@@ -36,8 +37,6 @@ limitations under the License.
<link rel="icon" type="image/png" sizes="32x32" href="{{ extendURL('/favicon-32x32.png')}}">
<link rel="icon" type="image/png" sizes="96x96" href="{{ extendURL('/favicon-96x96.png')}}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ extendURL('/favicon-16x16.png')}}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ extendURL('/img/thumb_up.png')}}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ extendURL('/img/thumb_up_liked.png')}}">
<link rel="manifest" href="{{ extendURL('/manifest.json')}}">
<meta name="msapplication-TileColor" content="#ea5455">
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
6 changes: 3 additions & 3 deletions src/frontend/views/login.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{#
{#
Copyright 2023 Dynatrace LLC
Licensed under the Apache License, Version 2.0 (the "License");
@@ -46,8 +46,8 @@ limitations under the License.
</div>

<div class="row">
<div class="col-md-6"><button class="btn btn-primary" formaction="{{ extendURL('/login') }}" type="submit" style="width: 100%;" name="login">Login</button></div>
<div class="col-md-6"><button class="btn btn-dark" formaction="{{ extendURL('/register') }}" type="submit" style="width: 100%;" name="register">Sign up</button></div>
<div class="col-md-6"><button class="btn btn-primary w-100" formaction="{{ extendURL('/login') }}" type="submit" name="login">Login</button></div>
<div class="col-md-6"><button class="btn btn-dark w-100" formaction="{{ extendURL('/register') }}" type="submit" name="register">Sign up</button></div>
</div>
</form>
</div>
31 changes: 10 additions & 21 deletions src/frontend/views/post.njk
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ limitations under the License.
<div class="row">
{% if (post.imageUrl) %}
<div class="col-auto">
<img src="{{ post.imageUrl }}" height="150px" alt="">
<img class="mw-100 h-100" src="{{ post.imageUrl }}" style="max-height: 150px" alt="">
</div>
{% endif %}
<div class="col">
@@ -45,27 +45,16 @@ limitations under the License.
</div>
</div>
<div class="card-footer">
<div style="float:right">
<div style="display: inline-block;">
<p style="text-align:right; height:30px; font-size:25px;">{{post.likeCount}}</p>
<div class="float-right">
<div class="d-inline-block">
<h6>{{post.likeCount}}</h6>
</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>
</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>
</form>
{% endif %}
<form action="{{extendURL('/unlike' if post.userLiked else '/like')}}" method="get" class="d-inline-block">
<input type="hidden" name="postId" value="{{post.postId}}">
<button type="submit" class="bg-transparent border-0">
<i class="bi {{ 'bi-hand-thumbs-up-fill' if post.userLiked else 'bi-hand-thumbs-up' }}" style="font-size: 1.25rem"></i>
</button>
</form>
</div>
</div>
</div>
6 changes: 3 additions & 3 deletions src/frontend/views/profile.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{#
{#
Copyright 2023 Dynatrace LLC
Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,13 +20,13 @@ limitations under the License.
<div>
<div class="d-flex align-items-center">
<div class="mr-2">
<img class="rounded-circle" style="background-color: white"
<img class="rounded-circle bg-white"
width="150" src="//robohash.org/{{ profileName }}.png?set=set1&size=150x150"
alt="{{ profileName }}">
</div>
<div class="ml-5">
<div class="h1 m-0">{{ profileName }}
<span style="vertical-align: text-top">
<span class="align-text-top">
{% if (membership !== 'PRO') %}
<a href="{{ extendURL('/membership') }}"><span class="membership-tag badge badge-light">
{{membership}}
4 changes: 1 addition & 3 deletions src/frontend/views/users.njk
Original file line number Diff line number Diff line change
@@ -43,9 +43,7 @@ limitations under the License.
</div>
</div>
<button type="submit" class="btn btn-primary ml-2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
<i class="bi bi-search"></i>
</button>
</div>
</div>
5 changes: 0 additions & 5 deletions src/like-service/.styleci.yml
Original file line number Diff line number Diff line change
@@ -6,8 +6,3 @@ php:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true
7 changes: 2 additions & 5 deletions src/like-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM php:8.0-cli

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY --from=composer:2.6.5 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www

@@ -12,10 +12,7 @@ ENV COMPOSER_ALLOW_SUPERUSER=1

COPY . /var/www

ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions opentelemetry-1.0.0RC1
RUN pecl install opentelemetry-1.0.0RC1 && docker-php-ext-enable opentelemetry

#This is faster https://github.com/composer/composer/issues/8205#issuecomment-507256979
RUN php /usr/bin/composer config --global repos.packagist composer https://packagist.org
60 changes: 20 additions & 40 deletions src/like-service/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
# Like Service

Provides REST endpoints for retrieving and updating like counts for posts in MariaDB:
* `POST like-service/like-service/like-delete`
```
Request body:
{
"postId": 123
}
```
* `POST like-service/like-service/like-post`
```
Request body:
{
"postId": 123
}
```
* `GET like-service/like-service/like-count/{postId}`
```
Response:
{
"likeCount": 123,
"userLiked": true
}
``````
* `GET like-service/like-service/like-count?postIds=1&postIds=2&postIds=3&postIds=4...`
* `POST like-service/like/123` (likes post with postId 123)
* `DELETE like-service/like?postId=123` (unlikes post with postId 123)
* `GET like-service/like?postId=1&postId=2&postId=3&postId=4...`
```
Response:
{
@@ -47,28 +27,28 @@ All the endpoints require that a JWT cookie be sent for authentication with the
The Like Service can either be run using `skaffold dev` (see [DEV-GUIDE](../../docs/DEV-GUIDE.md)), or it can be run locally, provided that you have the following requirements installed.
## Requirements
* PHP 8.0 with `opentelemetry` and MySQL (`mysqli`, `pdo`, `pdo_mysql`) extensions installed
* Composer
* PHP 8.0 with `opentelemetry` (e.g. version `1.0.0RC1`) and MySQL (`mysqli`, `pdo`, `pdo_mysql`) extensions installed
* Composer (e.g. version `2.6.5`; see [Installing Composer](https://getcomposer.org/download/))
* MariaDB instance (see [user-auth-service README](../user-auth-service/README.md) for setting it up)
## Environment Variables
The following environment variables need to be set:
| Name | Example Value | Description |
|---------------------------|-----------------------------------|-------------------------------------------------------------|
| SERVICE_NAME | unguard-like-service | Name of the service |
| API_PATH | /like-service | Api entrypoint path |
| SERVER_PORT | 8000 | The port that the server will run on |
| USER_AUTH_SERVICE_ADDRESS | unguard-user-auth-service-service | Change to hostname/IP of user-auth-service instance |
| DB_HOST | localhost | Address of MariaDB instance |
| DB_PORT | 3306 | Port of MariaDB instance |
| DB_DATABASE | likeDb | Database to create and use on the MariaDB instance |
| DB_USERNAME | root | Username of the MariaDB user |
| MARIADB_PASSWORD | | Password of the MariaDB user |
| JAEGER_DISABLED | true | Set to 'false' if you have a Jaeger instance running |
| JAEGER_COLLECTOR_HOST | collector | Change to hostname/IP of your Jaeger collector |
| JAEGER_PORT | 4318 | The jaeger collector port for HTTP OTLP traffic |
| JAEGER_SERVICE_NAME | unguard-like-service | Name that will be used for the service in the Jaeger traces |
| Name | Example Value | Description |
|---------------------------|---------------------------|-------------------------------------------------------------|
| SERVICE_NAME | unguard-like-service | Name of the service |
| API_PATH | /like-service | Api entrypoint path |
| SERVER_PORT | 8000 | The port that the server will run on |
| USER_AUTH_SERVICE_ADDRESS | unguard-user-auth-service | Change to hostname/IP of user-auth-service instance |
| DB_HOST | localhost | Address of MariaDB instance |
| DB_PORT | 3306 | Port of MariaDB instance |
| DB_DATABASE | likeDb | Database to create and use on the MariaDB instance |
| DB_USERNAME | root | Username of the MariaDB user |
| MARIADB_PASSWORD | | Password of the MariaDB user |
| JAEGER_DISABLED | true | Set to 'false' if you have a Jaeger instance running |
| JAEGER_COLLECTOR_HOST | collector | Change to hostname/IP of your Jaeger collector |
| JAEGER_PORT | 4318 | The jaeger collector port for HTTP OTLP traffic |
| JAEGER_SERVICE_NAME | unguard-like-service | Name that will be used for the service in the Jaeger traces |
## Setup
Install the required packages:
11 changes: 2 additions & 9 deletions src/like-service/app/Console/Kernel.php
Original file line number Diff line number Diff line change
@@ -27,20 +27,15 @@ class Kernel extends ConsoleKernel
*
* @var array
*/
protected $commands = [
//
];
protected $commands = [];

/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
}
protected function schedule(Schedule $schedule) {}

/**
* Register the commands for the application.
@@ -50,7 +45,5 @@ protected function schedule(Schedule $schedule)
protected function commands()
{
$this->load(__DIR__.'/Commands');

//require base_path('routes/console.php');
}
}
9 changes: 2 additions & 7 deletions src/like-service/app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
@@ -26,9 +26,7 @@ class Handler extends ExceptionHandler
*
* @var array
*/
protected $dontReport = [
//
];
protected $dontReport = [];

/**
* A list of the inputs that are never flashed for validation exceptions.
@@ -45,9 +43,6 @@ class Handler extends ExceptionHandler
*
* @return void
*/
public function register()
{
//
}
public function register() {}

}
4 changes: 2 additions & 2 deletions src/like-service/app/Http/Controllers/JaegerPropagator.php
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;

/**
* There's different formats used for propagation (reading/transmitting trace and span data to identify
* There are different formats used for propagation (reading/transmitting trace and span data to identify
* causal relationships between spans or, put differently, connect spans of different services)
* The PHP OpenTelemetry library seemingly only supports the W3C Trace Context format
* (https://www.w3.org/TR/trace-context/); that class is found here: OpenTelemetry\API\Trace\Propagation\TraceContextPropagator.
@@ -46,7 +46,7 @@
* The W3C traceparent is structured like this:
* version "-" trace-id "-" parent-id "-" trace-flags ------ version seems to always be 00, trace-id is a 32-digit hex string, parent-id is a 16-digit hex string, and trace-flags is a 2-digit hex string
*
* The Jaeger uber-trace-id is strcutured thus:
* The Jaeger uber-trace-id is structured thus:
* trace-id ":" span-id ":" parent-span-id ":" flags ------ trace-id is a 16- or 32-digit hex string, span-id is a 16-digit hex string, parent-span-id is a 16-digit hex string (and deprecated), flags is a 1- or 2-digit hex string
*
* The formats are very similar. Therefore, this class is mostly adapted from the official TraceContextPropagator class.
32 changes: 7 additions & 25 deletions src/like-service/app/Http/Controllers/LikeController.php
Original file line number Diff line number Diff line change
@@ -28,10 +28,9 @@ class LikeController extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

static function doLike($request)
static function doLike($request, $postId)
{
$user_token = $request->cookie('jwt');
$postId = $request->input('postId');

if (!self::validateToken($user_token)) {
return response()->json([
@@ -47,31 +46,14 @@ static function doLike($request)
return response()->make();
}

static function getLikeCountAndState($request, $postId)
static function getLikeCountsAndStates($request)
{
$user_token = $request->cookie('jwt');
$postIds = $request->query('postId', $request->query('postId[]', []));

if (!self::validateToken($user_token)) {
return response()->json([
'message' => 'Unauthorized'
], 401);
if(!is_array($postIds)) {
$postIds = [$postIds];
}
$userId = self::extractUserIdFromToken($user_token);

$count = DB::table('like')->where('postId', '=', $postId)->count('*');
$userLiked = DB::table('like')->where('userId', '=', $userId)->where('postId', '=', $postId)->count('*') > 0;


return response()->json([
'likeCount' => $count,
'userLiked' => $userLiked
], 200);
}

static function getMultipleLikeCountsAndStates($request)
{
$user_token = $request->cookie('jwt');
$postIds = $request->query('postIds', $request->query('postIds[]', []));

if (!self::validateToken($user_token)) {
return response()->json([
@@ -93,11 +75,11 @@ static function getMultipleLikeCountsAndStates($request)
* Function removeLike is vulnerable to SQL-Injection-Attacks.
* When "postId" is an array, all contents of the array are added to the SQL-Bindings of the subsequent query.
* This means that, when passing an array it is possible to manipulate the userId value of the query -> unlike another user's like.
*/
*/
static function removeLike($request)
{
$user_token = $request->cookie('jwt');
$postId = $request->input('postId');
$postId = $request->query('postId');

if (!self::validateToken($user_token)) {
return response()->json([
10 changes: 2 additions & 8 deletions src/like-service/app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
@@ -26,18 +26,12 @@ class AppServiceProvider extends ServiceProvider
*
* @return void
*/
public function register()
{
//
}
public function register() {}

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
public function boot() {}
}
19 changes: 0 additions & 19 deletions src/like-service/package.json

This file was deleted.

2 changes: 0 additions & 2 deletions src/like-service/phpunit.xml
Original file line number Diff line number Diff line change
@@ -21,8 +21,6 @@
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
<!-- <server name="DB_DATABASE" value=":memory:"/> -->
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
Empty file.
14 changes: 5 additions & 9 deletions src/like-service/routes/web.php
Original file line number Diff line number Diff line change
@@ -33,18 +33,14 @@
include (__DIR__ . '/../app/Http/Controllers/LikeController.php');


Route::get('/like-service/like-count', function (Request $request){
return LikeController::getMultipleLikeCountsAndStates($request);
Route::get('/like', function (Request $request){
return LikeController::getLikeCountsAndStates($request);
});

Route::get('/like-service/like-count/{postId}', function (Request $request, string $postId){
return LikeController::getLikeCountAndState($request, $postId);
});

Route::post('/like-service/like-delete', function (Request $request){
Route::delete('/like', function (Request $request){
return LikeController::removeLike($request);
});

Route::post('/like-service/like-post', function(Request $request){
return LikeController::doLike($request);
Route::post('/like/{postId}', function(Request $request, string $postId){
return LikeController::doLike($request, $postId);
});
17 changes: 0 additions & 17 deletions src/like-service/webpack.mix.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/malicious-load-generator/locustfile.py
Original file line number Diff line number Diff line change
@@ -176,7 +176,7 @@ def post_sql_php(self):
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())
self.client.get("/unlike", params={'postId': [post_id, user_id]}, headers=self.get_random_x_forwarded_for_header())
time.sleep(1)

def on_start(self):
Original file line number Diff line number Diff line change
@@ -154,7 +154,7 @@ public Post getPost(@PathVariable("postid") String postId, @CookieValue(value =
if (post == null) {
throw new ResponseStatusException(NOT_FOUND, "Post not found.");
}
postSerializer.serializePost(new SerializedPost(post.getUsername(), post.getBody(), post.getImageUrl(), post.getTimestamp(), postId, UUID.randomUUID()));
postSerializer.serializePost(new SerializedPost(postId, post.getUsername(), post.getBody(), post.getImageUrl(), post.getTimestamp(), UUID.randomUUID()));
return post;
}

Original file line number Diff line number Diff line change
@@ -21,23 +21,27 @@
import com.fasterxml.jackson.annotation.JsonProperty;

public class Post {
private final String postId;
private final String username;
private final String body;
private final Date timestamp;
private final String imageUrl;
private final String postId;

public Post(
@JsonProperty("postId") String postId,
@JsonProperty("username") String username,
@JsonProperty("body") String body,
@JsonProperty("imageUrl") String imageUrl,
@JsonProperty("timestamp") Date timestamp,
@JsonProperty("postId") String postId) {
@JsonProperty("timestamp") Date timestamp) {
this.postId = postId;
this.username = username;
this.body = body;
this.imageUrl = imageUrl;
this.timestamp = timestamp;
this.postId = postId;
}

public String getPostId() {
return postId;
}

public String getUsername() {
@@ -48,15 +52,11 @@ public String getBody() {
return body;
}

public Date getTimestamp() {
return timestamp;
}

public String getImageUrl() {
return imageUrl;
}

public String getPostId() {
return postId;
public Date getTimestamp() {
return timestamp;
}
}
Original file line number Diff line number Diff line change
@@ -26,13 +26,13 @@ public class SerializedPost extends Post {
private final UUID serialId;

public SerializedPost(
@JsonProperty("postId") String postId,
@JsonProperty("username") String username,
@JsonProperty("body") String body,
@JsonProperty("imageUrl") String imageUrl,
@JsonProperty("timestamp") Date timestamp,
@JsonProperty("postId") String postId,
@JsonProperty("serialId") UUID serialId) {
super(username, body, imageUrl, timestamp, postId);
super(postId, username, body, imageUrl, timestamp);
this.serialId = serialId;
}

Original file line number Diff line number Diff line change
@@ -196,7 +196,7 @@ private Post readAndTransformPost(String jwtToken, String postId, Jedis jedis) t
String imageUrl = postMap.get("imageUrl");
Date timestamp = new Date(Long.parseLong(postMap.get("time")));

return new Post(userName, body, imageUrl, timestamp, postId);
return new Post(postId, userName, body, imageUrl, timestamp);
}

public List<Post> getTimeline(String jwtToken) {
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ class PostSerializerTest {
@Test
void serializePost_ReturnsTrue_WhenObjectCouldBeDeserialized() {
assertThat(postSerializer.serializePost(
new SerializedPost("username", "body", "imageURL", new Date(), "1", UUID.randomUUID())))
new SerializedPost("1", "username", "body", "imageURL", new Date(), UUID.randomUUID())))
.isTrue();
}
}

0 comments on commit fb332cc

Please sign in to comment.