// Check if user is logged in if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit(); }
// Check if session is expired if (time() > $_SESSION['expires']) { header("Location: logout.php"); exit(); }
// Display user ID on landing page echo"Welcome, User ID: " . urlencode($_SESSION['user_id']);
// Get the user for admin $db = newPDO('sqlite:memory:'); $admin = $db->query('SELECT * FROM users WHERE user_id = 0 LIMIT 1')->fetch();
// Check if the user is admin if ($admin['user_id'] === $_SESSION['user_id']) { // Read the flag from flag.txt $flag = file_get_contents('flag.txt'); echo"<h1>Flag</h1>"; echo"<p>$flag</p>"; } else { // Display the source code for this file echo"<h1>Source Code</h1>"; highlight_file(__FILE__); }
?>
从代码逻辑可以看出当执行sql语句SELECT * FROM users WHERE user_id = 0 LIMIT 1后第一行user_id的值和当前网页会话SESSION['user_id']相等时回显flag,从SQL查询语句容易得到就是要让SESSION['user_id']值为0,退出登录去logout.php,重新点注册,是session的话就说明是在提交表单环节设置的,bp抓个包,改post参数user_id为0,Forward。
// Check if user is logged in if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit(); }
// Check if session is expired if (time() > $_SESSION['expires']) { header("Location: logout.php"); exit(); }
// Display user ID on landing page echo"Welcome, User ID: " . urlencode($_SESSION['user_id']);
// Get the user for admin $db = newPDO('sqlite:memory:'); $admin = $db->query('SELECT * FROM users WHERE username = "admin" LIMIT 1')->fetch();
// Check user_id if (isset($_GET['user_id'])) { $user_id = (int) $_GET['user_id']; // Check if the user is admin if ($user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])) { // Read the flag from flag.txt $flag = file_get_contents('/flag.txt'); echo"<h1>Flag</h1>"; echo"<p>$flag</p>"; } }
// Display the source code for this file echo"<h1>Source Code</h1>"; highlight_file(__FILE__); ?>
看下面这部分:
1 2 3 4 5 6 7 8 9 10 11 12 13
$admin = $db->query('SELECT * FROM users WHERE username = "admin" LIMIT 1')->fetch();
// Check user_id if (isset($_GET['user_id'])) { $user_id = (int) $_GET['user_id']; // Check if the user is admin if ($user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])) { // Read the flag from flag.txt $flag = file_get_contents('/flag.txt'); echo"<h1>Flag</h1>"; echo"<p>$flag</p>"; } }
if (str_contains($filename, "/") or str_contains($filename, "."))条件防止任意文件读取,但是注意看$filename是经过参数file进行url解码得到的,PHP在获取GET参数的时候解码一次(获取POST参数时不会),算上else里解码一次,总共需要进行三次解码,两次解码过后$filename不能含有/和.字符,因此,总共需要经过三次url编码,使得在不破坏url结构的同时能够绕过字符串过滤达到文件读取,那读什么文件呢,看下dockerfile,存在:
db.get('SELECT * FROM users WHERE username = "' + username + '" and password = "' + password+ '"', (err, row) => { if (err) { console.error(err); res.status(500).send('Error retrieving user'); } else { if (row) { req.session.loggedIn = true; req.session.username = username; res.send('Login successful!'); } else { res.status(401).send('Invalid username or password'); } } }); }); .......... app.get('/flag', (req, res) => { if (req.session.username == "admin") { res.send('Welcome admin. The flag is ' + fs.readFileSync('flag.txt', 'utf8')); } elseif (req.session.loggedIn) { res.status(401).send('You must be admin to get the flag.'); } else { res.status(401).send('Unauthorized. Please login first.'); } });
'SELECT * FROM users WHERE username = "'拼接username拼接'" and password = "'拼接password拼接'"'最终username和password均为双引号闭合
随便一个常规字符输入会显示Invalid username or password,但是传入双引号后出现Error retrieving user报错回显,这里注意,***sqlite3和Oracle不支持#号单行注释,只能使用–***,传入"--,报错消失,再次变为Invalid username or password,探路完成,下面看源代码:
flag = '' pre = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!?1234567890}{_" for re inrange(71,0,-1): for i in pre: #print(flag) password = "a" * re username = "a" * re + flag + i