Windows和Linux换行符不同
这个问题在long long ago我就遇到了,也幸亏如此这次很快就发现了。暑假我留校和张城赫写一起属于我们的C2程序SuperHacker的时候我遇到过类似问题,当时我是一个纯纯的Golang小白,当然现在也是,当时我遇到一个问题就是我在windows下测试输入时发现无论我输入什么东西程序都没法接受输入,当时我写的代码是(其他的记不太清了):
user_input=strings.TrimSuffix(user_input,"\n")
作用嘛很好理解,就是想过滤掉用户输入的换行符。结果我试的时候怎么都接受不了输入,然后我去找了个学校工作室里专门做golang方向的学长讨论,他却说我的代码本地测试的时候完全没问题,他也不知道我这儿哪儿出错了,然后我去问凯神,他一下就发现问题了,答案很简单,windows下的换行符是”\r\n”,而Linux下的换行符才是”\n”,于是把我的代码改成了:
user_input=strings.TrimSuffix(user_input,"\n")
user_input=strings.TrimSuffix(user_input,"\r")
果然问题迎刃而解,然后我去问最初的那个学长,他当时是用mac测试的,所以没发现这个问题。时间跳转到这次比赛,我在写pwn的脚本时也出现了这种问题,先看看当时的报错吧:
我很确信我连上了而且可以执行命令,但怎么执行命令都不对,总有个碍眼的”\r”阻挡我命令的正确执行,这次我一下就联想到这个换行符的问题了,多的这个\r不就和我上次写c2的时候犯的问题一样吗,肯定是代码只获取了\n之前的输入,把\r忽略了,最后我换到我的kali上执行果然成功了:
非常曲折的做题之旅
暴躁老哥的作业系统这个题基本上把我做成🤡了,一路上踩了很多很多坑,要不是学长最后放了hint可能我过了很久也不会把思路转入正路,还在想那些花里胡哨的构造,记得我把wp上传上去之后学长对我的wp做出了高度评价:
有兴趣的可以去看看协会群里我发的wp,不过其实也没什么好看的,主要就是漫长的试错过程,如果只是想看解题思路看这个或者wp的末尾其实也行,这里来分析一下我当时做题时的错误思路以及为什么那些方法没法成功以及一些其他有趣的思考。做出来了之后回头再看向这个题,其实出题人的各种提示都挺明显的,确实只是一道给新生入门sql的入门题。
首先进入主页,是个文件上传的页面:
因为之前已经做了一道文件上传的题了,所以我一开始就知道肯定不是靠文件上传拿shell,多半是个sql注入,就这个点没考过了。而且我试了下上传,限制挺多的,是白名单,只能上传.doc。如果只是黑名单像之前过滤了ph,ini,htaccess
的话其实还是很好做的,比如用shtml来利用SSI注入RCE(Baby_Upload那个题)或者经典的phar(前面有文章详细分析,这里不再赘述),不过这里这些方法当然不行啦,只能白名单,所以说真实写项目的时候过滤东西还是用白名单最好,天知道恶意的攻击者会用什么稀奇古怪的文件拿下你的网站。
放弃了文件上传这种想法,我注意到另外两个参数,一个是姓名一个编号,因为我认为这是一道sql题,因此他们肯定会被插入数据库中,所以我试着对这两个参数跑了很久的sqlmap,自己也构造了很久,发现似乎并没有什么方法拿到数据,遂放弃了这种想法。因为我fuzz的时候发现有些路径显示了403 Forbidden,所以我猜测这个网站还是有其他很多东西的,只是没显示出来,所以拿御剑跑了一下,果然一下就跑出后台了,路径:/admin_login.php:
看到这么一个简陋的后台登陆系统你的想法是什么?当然是直接开始爆破了,于是我以yyz,admin,root,superroot,v4eky跑了一个我在github上找的某热门字典的top10000,结果没爆出来(只能说这个字典真的垃圾,之前出去打比赛用这个也啥也没爆出来),于是我放弃了爆破这种想法,把思路投向了盲注,然后发现用户名里过滤了\和引号,如果这两个没被过滤的话其实是可以用\构造万能密码登录或者union插入临时表登录。不过因为这里把路堵死了嘛,所以我当时就直接考虑盲注了,用用户名盲注的时候因为过滤的东西太多了没注出来,但是密码这个参数没有做任何过滤,我以为能用这个打,但注了很久很久还是没有成功,这其中还尝试了cookie和useragent的注入,当然也失败了。直到出题人放hint,说本题是个弱密码,我才换了个字典爆破,结果一下就爆破出来了,密码是root123(这个字典确实挺好用的,misc里一个题爆压缩包密码也一下就爆破出来了)
但因为我不认为这个题是文件上传的题目,所以前台里什么都没上传,所以我进入后台时后台是空的,这也极大的干扰了我的思路:
这让我产生了一个错误的想法,那就是这个后台确实什么用都没有,重要的是登录成功这个标志,所以我当时就继续登录框尝试以是否成功登录构造布尔注入,最后当然是没有任何收获。后来我才意识到一个很严重的问题,如果这个上传功能没有用的话出题人何必写这个功能呢,难道是为了干扰我们视线吗?于是我在前台随便上传了一个文件,这才发现这个后台别有洞天:
这个后台竟然是可以管理文件的!我们点击一下这个文件试试:
可以看见这个url变成了file_checked.php?filename=781.doc,然后我们就可以下载查看这个文件或者删除这个文件,不过这两个功能其实都没有什么用,点击下载查看只是路径变成/uploads/781.doc这样我们就可以下载这个文件了,而点击删除文件肯定会出现与数据库的交互,所以我思考怎么从这个删除功能拿到我们想要的数据,于是我构造了一下文件名,比如781.doc and select database();.doc,但当我再点击这个文件时意外却发生了:
我明明成功上传了这个文件,为什么这里会显示不存在呢?这是一个很值得思考的问题,但我当时只是简单的认为大概有过滤吧过滤了我这个特殊文件,却没去思考到底是什么过滤,其实解决问题最简单的方法就是去看下报错,到底是什么文件消失了就行了,但我当时却没有细想,只是继续构造文件,直到后面发现是select被过滤了,于是尝试双写,一些就绕过去了,后面又发现union也被过滤了,明白这道题肯定是个union联合注入题。
尝试很久直接在file_checked.php后面对文件直接union注入但还是失败了,最后才想到可能是靠构造文件名注入。对于后台的判断我是这么想的:对于yyz.doc,首先file_exists(yyz.doc),如果这个文件确实存在再放到数据库里查询,语句是select yyz.doc from xxx where之类之类的。然后我最先的思路是什么呢,是思考这个file_exists函数有没有什么特性能恒为真,如果它能恒为真那我岂不是就可以为所欲为了,这时一篇文章引起了我的注意:
于是我天真的以为这种想法是可以实现的,于是按照他所说的尝试了一下,结果完全就没法,可恶,不知道他那个题到底是个什么情况。然后我开始思考怎么闭合末尾的.doc,首先我想到的方法是构造一个1.doc’;select database();.doc,这样sql的查询语句就变成了select ‘1.doc’;select database();.doc’,这样就形成一个堆叠注入,从这里开始我的思路已经完全歪了,有两个大问题,第一,在数据库里搜索”1.doc”这种带有文件后缀名的情况我从来就没有见过,而事实上后台真实的查询语句确实是只查询了”1″,也就是截取了.doc前面的部分;其次,有个非常非常严重的问题,这导致了我后面有些本地成功的构造在线上也查不到数据,那就是———这个题只能联合注入,因为我们这里能看到数据的框口是这个注释,所以只有堆叠注入查询两条数据我才能看到回显,否则即使用某种构造让数据跟着查询成功1.doc出来我们也看不到,那里的数据在第一个框口。
或许很难理解,我这里放出正确答案,我最后成功的构造是1.doc’ union select database() ‘.doc,当然这个构造也不能算绝对正确,至少和预期解不同,这也导致我后面用了其他比较刁钻方法才拿到数据。
我们不妨去数据库看看执行结果:
我们假设这个1就是查询文件,那么第二个数据database()会是在另一行新的查询中。而在我的试错中,我尝试了很多构造,我们来看看为什么他们不能成功。
当时我最先想的堆叠注入嘛,所以我先想到的语句是:1.doc’;select database();.doc,当然,我们现在知道数据库里查询的是截取.doc前面那个部分,也就是1,我这个方法就算构造出来肯定也拿不到数据,所以我在这里只是给大家分享一下我的思路,或许能在其他sql注入相关的题目里有所帮助。我当时其实不知道这种用分号直接分割错误语句能不能成功执行,于是我先找了个在线平台试了试:
可以看到虽然有报错,但还是执行成功了的,于是我以为这种方法是可以的,于是拿到题目环境里尝试,但是怎么都拿不到数据,最后我本地试了下,这才知道这样是不行的:
然后我想到能不能用其他方法闭合末尾的”.doc”,最简单的就是注释符嘛,比如#,–+,/*。
然而这些符号各有各的缺点,特别是那种同时在php和sql中充当注释符的符号即#和/*, 他们会导致文件名直接少了一段,根本就查不到这个文件:
可以看到既然存在了#,文件也就被截断了,根本就不能存在这个文件,想要从注释里拿到数据也只能是无稽之谈。而/*还要特殊一点,在php里但走一个/*后面的所有东西就都变成注释了,而sql里必须前后对应,既有/*又有*/,否则就报错:
而–+比较煞笔,两个减号-连在一起就自动变成空格,用编码也不行,因此这种想法是走不通的。
然后我终于想起来这个题是联合注入了,于是构造出来了12.doc union select database() union select 31.doc这种,然而没有查到数据。
当然,这时候我忽略了引号,也忽略了查询语句截取的是前面部分这个问题,当时我本地测试的时候是可以成功的:
那时因为没查到数据所以放弃了这种思路,现在我们再来思考这种构造方式能不能成功呢,当然是可以的,只要把那两个问题解决一下就行了,闭合一下单引号和注意是截取.doc前面的部分:
43' union select database() union select '.doc
这时后台的查询语句是:select ’43’ union select database() union select ‘.doc’,所以只要存在个43.doc就能满足条件拿到数据了。
放弃这个思路后,我从网上找到思路发现了一种本地可以成功但线上失败的方法:
我们来思考为什么这种构造查不出数据,是因为那个15.doc吗?我们改成纯数字试试:
为什么还是没有数据呢?🤔🤔这就说到了上面那个问题。在一般我们做sql注入题目的时候都需要先用order by来判断列数,然后用类似select 1,database(),3,4的方法来拿到数据,这个1,3,4确实没有什么用,但需要将它做到占位的位置,一个网站可能有好几个看数据的窗口,但只有那个2是我们可以拿到sql注入结果的,而这种方法的数据都集中在第一列里去了,而那个注释是在第二列,所以这种做法不行,我们只能用联合注入。
直到最后我才想到了正确的构造方法,但当我想查完数据库想要继续查询时,却发现自己怎么什么都查不出来:
这又是为什么呢?明明语句都是正确的,为什么刚刚能查出来数据库,现在却什么都查不到,难道是因为没有information_schema吗?🤔🤔那时我的想法是或者这个数据库不是常见的sql,而是redis,MongoDB等其他数据库,之所以我能用select database()查出数据其实只是一个美妙的巧合。于是我试着用select version()查了下数据库的版本和名字,发现他是MariaDB。
但我其实不懂这是啥,于是去网上搜了一篇其他人注入MariaDB的过程,发现其中运用了双注入这种方法。
我其实也不懂这方法是啥,只是照猫画虎,最后还是成功把这道题做出来了:
'union select (select group_concat(table_name) from information_schema.tables where table_schema=database())'.doc
'union select (select group_concat(column_name) from information_schema.columns where table_name='flag')'.doc
'union select (select group_concat(flag)) from flag)'.doc
不过事实上当比赛后我看了官方wp后发现其实这并不是预期解,学长也没先想过用个什么其他数据库为难我们,这一切其实只是一个美丽的巧合。
预期解的闭合语句是:
' and 1=2 union select xxx from xxx where '.doc'='.doc
这样后台接受到的数据就变成了
select "1" and 1=2 union select xxx from xxx where '.doc'='.doc'
自然就可正常查询了。
而这里的双注入其实是只是为了分割那个’.doc’,防止语句被干扰罢了