使用Go/Python登录网站时,使用CSRF令牌进行身份验证。

huangapple go评论86阅读模式
英文:

Login to site using CSRF token using Go/Python

问题

我想要自动备份一个需要登录的网站的网页内容。我尝试通过模拟POST请求来登录。但是我遇到了错误:

csrf token: CSRF攻击检测到

以下是我使用的代码的一些摘录:

func postLoginForm(csrfToken string) {
	values := make(url.Values)
    values.Set("signin[username]", "myusername") 
    values.Set("signin
		
输入密码查看隐藏内容

", "mypassword") values.Set("signin[_csrf_token]", csrfToken) resp, err := http.PostForm("https://spwebservicebm.reaktor.no/admin/nb", values) dumpHTTPResponse(resp) // 将响应显示到STDOUT }

我通过获取登录页面并扫描其中一个名为signin[_csrf_token]的隐藏输入字段来获取csrf token。执行此操作的代码的重要部分如下:

// 查找名为signin[_csrf_token]的输入字段,并将其值作为csrfToken返回
func handleNode(n *html.Node) (csrfToken string, found bool) {
	if n.Type == html.ElementNode && n.Data == "input" {
		m := make(map[string]string)
		for _, attr := range n.Attr {
			m[attr.Key] = attr.Val
		}
		if m["name"] == "signin[_csrf_token]" {
			return  m["value"], true
		}
	}
	
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		 if csrfToken, found = handleNode(c); found {
			 return 
		 }		 
	}
	
	return "", false
}

我不一定非要使用Go语言,只是因为我最熟悉它。使用Python也可以是一个解决方案,但是我在那方面也没有更多的运气。

英文:

I want to backup automatically web content from a site which requires login. I try to login by simulating a POST request. But I get the error:

csrf token: CSRF attack detected

Here are some extracts from the code I use:

func postLoginForm(csrfToken string) {
	values := make(url.Values)
    values.Set("signin[username]", "myusername") 
    values.Set("signin
		
输入密码查看隐藏内容

", "mypassword") values.Set("signin[_csrf_token]", csrfToken) resp, err := http.PostForm("https://spwebservicebm.reaktor.no/admin/nb", values) dumpHTTPResponse(resp) // show response to STDOUT }

The csrf token I get by fetching the login page and scanning it for a hidden input field named signin[_csrf_token]. The important part of the code for doing that is the following:

// Finds input field named signin[_csrf_token] and returns value as csrfToken
func handleNode(n *html.Node) (csrfToken string, found bool) {
	if n.Type == html.ElementNode && n.Data == "input" {
		m := make(map[string]string)
		for _, attr := range n.Attr {
			m[attr.Key] = attr.Val
		}
		if m["name"] == "signin[_csrf_token]" {
			return  m["value"], true
		}
	}
	
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		 if csrfToken, found = handleNode(c); found {
			 return 
		 }		 
	}
	
	return "", false
}

I don't need to be using Go, that is just because I am most familiar with that. Using python could be a solution as well, but I did not have any more luck with that.

答案1

得分: 5

问题在于Go 1.2不会自动为其HTTP请求使用cookie jar。第一个请求是从登录页面获取CSRF令牌。第二个请求是使用该CSRF令牌进行登录的POST请求。但是,由于第二个请求的HTTP头中没有附加会话cookie,服务器不知道这是同一个程序尝试登录。因此,服务器认为这是一次CSRF尝试(您从其他地方选择了CSRF令牌并尝试重用它)。

因此,为了获取登录页面并提取CSRF令牌,我们首先创建自己的客户端对象。否则,我们无处可附加cookie jar。http.PostForm确实提供了对cookie jar的访问:

client = &http.Client{}

创建一个在https://stackoverflow.com/questions/11361431/authenticated-http-client-requests-from-golang中描述的cookie jar。这比官方的http://golang.org/pkg/net/http/cookiejar/ Cookie Jar更容易设置和调试。

jar := &myjar{}
jar.jar = make(map[string][]*http.Cookie)
client.Jar = jar	

resp, err := client.Get("https://spwebservicebm.reaktor.no/admin")		
doc, err := html.Parse(resp.Body)

然后,为了登录,我们重用附加了cookie jar的客户端对象:

values := make(url.Values)
values.Set("signin[username]", "myusername")
values.Set("signin
		
输入密码查看隐藏内容

", "mypassword") values.Set("signin[_csrf_token]", csrfToken) resp, err := client.PostForm("https://spwebservicebm.reaktor.no/admin/login", values)

您会注意到,代码与问题中的代码几乎相同,只是我们使用了client.PostForm而不是http.PostForm

感谢https://stackoverflow.com/questions/11361431/authenticated-http-client-requests-from-golang中的dommage和答案,让我找到了正确的解决方法。

英文:

The issue is that Go 1.2 does not automatically use a cookie jar for its HTTP requests. The first request is to get the CSRF token from the login page. The second request is to POST a login using that CSRF token. But since no session cookie is attached to the HTTP header on the second request the server does not know that it is the same program trying to login. Thus the server thinks it is a CSRF attempt (that you picked the CSRF token from somewhere else and tried to reuse it).

So to get login page and extract CSRF token, we first create our own client object. Otherwise we have nowhere to attach the cookie jar. http.PostForm does give access to cookie jar:

client = &http.Client{}

Create a cookie Jar described in https://stackoverflow.com/questions/11361431/authenticated-http-client-requests-from-golang . This was easier to setup and debug than the official: http://golang.org/pkg/net/http/cookiejar/ Cookie Jar

jar := &myjar{}
jar.jar = make(map[string] []*http.Cookie)
client.Jar = jar	

resp, err := client.Get("https://spwebservicebm.reaktor.no/admin")		
doc, err := html.Parse(resp.Body)

Then to login, we reuse the client object with the cookie jar attached to it:

values := make(url.Values)
values.Set("signin[username]", "myusername")
values.Set("signin
		
输入密码查看隐藏内容

", "mypassword") values.Set("signin[_csrf_token]", csrfToken) resp, err := client.PostForm("https://spwebservicebm.reaktor.no/admin/login", values)

You'll notice that the code is almost identical to the one in the question except we use client.PostForm instead of http.PostForm.

Thanks to dommage and answer to https://stackoverflow.com/questions/11361431/authenticated-http-client-requests-from-golang for getting me on right track.

答案2

得分: 0

你可以使用BeautifulSoup来抓取令牌,并将其存储在头部或发送到服务器中。你可以像这样做:

from requests import session
from bs4 import BeautifulSoup

def authent(self):
    ld('trying to get token...')
    r = self.session.get(BASE_URL, headers=FF_USER_AGENT)
    soup = BeautifulSoup(r.content)
    elgg_token = soup.select('input[name="__elgg_token"]')[0]["value"]
    elg_ts = soup.select('input[name="__elgg_ts"]')[0]["value"]
    payload["__elgg_token"] = elgg_token # 我将其发送到服务器...
    payload["__elgg_ts"] = elg_ts
    r = self.session.post(LOGIN_URL, data=payload, headers=FF_USER_AGENT)
    if r.url != DASHBOARD_URL:
        raise AuthentError("Error")

请注意,这只是代码的翻译部分,不包括任何其他内容。

英文:

You can scrape the token using beautifulsoup and store it, either in the header or send it to the server. You can do something like this:

from requests import session
from bs4 import BeautifulSoup

def authent(self):
    ld('trying to get token...')
    r = self.session.get(BASE_URL, headers=FF_USER_AGENT)
    soup = BeautifulSoup(r.content)
    elgg_token = soup.select('input[name="__elgg_token"]')[0]["value"]
    elg_ts = soup.select('input[name="__elgg_ts"]')[0]["value"]
    payload["__elgg_token"] = elgg_token # I sent it to the server...
    payload["__elgg_ts"] = elg_ts
    r = self.session.post(LOGIN_URL, data=payload, headers=FF_USER_AGENT)
    if r.url != DASHBOARD_URL:
      raise AuthentError("Error")

huangapple
  • 本文由 发表于 2014年1月20日 19:21:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/21232665.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定