使用cURL登录Gitlab

时间:2017-12-23 00:58:17

标签: curl gitlab csrf

为了针对Docker中的Gitlab实例测试命令行工具,我想使用用户名和密码登录Gitlab并获取创建的会话以验证我的API请求。

因此我会做以下事情:

  1. 使用curl -i http://localhost:8080/users/sign_in -s
  2. 卷曲用户登录页面
  3. 从标题
  4. 中获取_gitlab_session
  5. 从登录表单
  6. 获取authenticity_token
  7. 使用
  8. 发送第二个curl请求
    
    curl 'http://localhost:8080/users/sign_in' \
      -H "_gitlab_session=${cookie}" \
      -H 'Origin: http://localhost:8080' \
      -H 'Content-Type: application/x-www-form-urlencoded' \
      -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' \
      -H 'Referer: http://localhost:8080/users/sign_in' \
      -H 'Connection: keep-alive' \
      --data-urlencode "authenticity_token=${token}" \
      --data-urlencode "user[login]=root" \
      --data-urlencode "user[password]=12341234" \
      --data-urlencode "user[remember_me]=0"
    

    但是我没有有效的用户登录,而是

    422 - The change you requested was rejected.
    

    在日志文件中,我看到了

    ==> gitlab-rails/production.log <==
    Started POST "/users/sign_in" for 172.17.0.1 at 2017-12-23 00:22:16 +0000
    Processing by SessionsController#create as HTML
    Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"login"=>"root", "password"=>"[FILTERED]", "remember_me"=>"0"}}
    Can't verify CSRF token authenticity
    Completed 422 Unprocessable Entity in 125ms (ActiveRecord: 7.8ms)
    
    ==> gitlab-rails/production_json.log <==
    {"method":"POST","path":"/users/sign_in","format":"html","controller":"SessionsController",  
    "action":"create","status":422,"error":" 
    ActionController::InvalidAuthenticityToken:ActionController::InvalidAuthenticityToken",
    "duration":126.29,"view":0.0,"db":7.78,"time":"2017-12-23T00:22:16.039Z",
    "params":{"authenticity_token":"[FILTERED]","user":{"login":"root","password":"
    [FILTERED]","remember_me":"0"}},"remote_ip":"172.17.0.1",
    "user_id":1,"username":"root"}
    
    ==> gitlab-rails/production.log <==
    
    ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
      lib/gitlab/middleware/multipart.rb:93:in `call'
      lib/gitlab/request_profiler/middleware.rb:14:in `call'
      lib/gitlab/middleware/go.rb:18:in `call'
      lib/gitlab/etag_caching/middleware.rb:11:in `call'
      lib/gitlab/middleware/read_only.rb:31:in `call'
      lib/gitlab/request_context.rb:18:in `call'
      lib/gitlab/metrics/requests_rack_middleware.rb:27:in `call'
    

    我认为我忘了在第二次请求中传递必要的成分 - 但我无法弄明白,哪一个。

4 个答案:

答案 0 :(得分:9)

通过其他答案和评论的一些有用提示,我终于想出了这个解决方案:

gitlab_host="http://localhost:8080"
gitlab_user="root"
gitlab_password="12341234"

# curl for the login page to get a session cookie and the sources with the auth tokens
body_header=$(curl -c cookies.txt -i "${gitlab_host}/users/sign_in" -s)

# grep the auth token for the user login for
#   not sure whether another token on the page will work, too - there are 3 of them
csrf_token=$(echo $body_header | perl -ne 'print "$1\n" if /new_user.*?authenticity_token"[[:blank:]]value="(.+?)"/' | sed -n 1p)

# send login credentials with curl, using cookies and token from previous request
curl -b cookies.txt -c cookies.txt -i "${gitlab_host}/users/sign_in" \
    --data "user[login]=${gitlab_user}&user[password]=${gitlab_password}" \
    --data-urlencode "authenticity_token=${csrf_token}"

# send curl GET request to personal access token page to get auth token
body_header=$(curl -H 'user-agent: curl' -b cookies.txt -i "${gitlab_host}/profile/personal_access_tokens" -s)
csrf_token=$(echo $body_header | perl -ne 'print "$1\n" if /authenticity_token"[[:blank:]]value="(.+?)"/' | sed -n 1p)

# curl POST request to send the "generate personal access token form"
# the response will be a redirect, so we have to follow using `-L`
body_header=$(curl -L -b cookies.txt "${gitlab_host}/profile/personal_access_tokens" \
    --data-urlencode "authenticity_token=${csrf_token}" \
    --data 'personal_access_token[name]=golab-generated&personal_access_token[expires_at]=&personal_access_token[scopes][]=api')

# Scrape the personal access token from the response HTML
personal_access_token=$(echo $body_header | perl -ne 'print "$1\n" if /created-personal-access-token"[[:blank:]]value="(.+?)"/' | sed -n 1p)

根据GitLab API documentation,您现在可以使用会话cookie来验证API请求:

curl --header "Private-Token: ${personal_access_token}" https://gitlab.example.com/api/v4/projects

一些提示:

答案 1 :(得分:1)

$ {cookie}中究竟是什么?在我的测试中,登录页面有4个cookie,其中3个看起来像令牌,如果成功登录需要至少1个以上的cookie而不是_gitlab_session cookie,我不会感到惊讶。但是我没有费心去找到所需cookie的正确组合,我认为最好让curl自动处理cookie,例如--cookie-jar和--cookie(然后所有的cookie应该是正确的) - 更多,登录页面上有3种不同的authenticity_token,您确定要获取正确的吗?也许你的问题是你拿错了令牌。区分3个令牌非常困难,我建议使用脚本语言。

这是使用hhb_curl在PHP中测试的工作示例(只需替换第10行和第11行的用户名和密码):

<?php
declare(strict_types = 1);
require_once ('hhb_.inc.php');
$hc = new hhb_curl ( '', true );
$html = $hc->exec ( 'https://gitlab.com/users/sign_in' )->getStdOut ();
$domd = @DOMDocument::loadHTML ( $html );
$inputs = getDOMDocumentFormInputs ( $domd, true, false ) ['new_user'];
// var_dump ( $inputs );
$inputs ['user[login]'] = '???';
$inputs ['user[password]'] = '???';
$html = $hc->setopt_array ( array (
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => http_build_query ( $inputs ) 
) )->exec ()->getStdOut ();
echo $html;
$domd = @DOMDocument::loadHTML ( $html );
$xp = new DOMXPath ( $domd );
foreach ( $xp->query ( '//div[contains(@class,"flash-alert")]' ) as $loginError ) {
    var_dump ( "login error: ", trim($loginError->textContent) );
}

function getDOMDocumentFormInputs(\DOMDocument $domd, bool $getOnlyFirstMatches = false, bool $getElements = true): array {
    // :DOMNodeList?
    if (! $getOnlyFirstMatches && ! $getElements) {
        throw new \InvalidArgumentException ( '!$getElements is currently only implemented for $getOnlyFirstMatches (cus im lazy and nobody has written the code yet)' );
    }
    $forms = $domd->getElementsByTagName ( 'form' );
    $parsedForms = array ();
    $isDescendantOf = function (\DOMNode $decendant, \DOMNode $ele): bool {
        $parent = $decendant;
        while ( NULL !== ($parent = $parent->parentNode) ) {
            if ($parent === $ele) {
                return true;
            }
        }
        return false;
    };
    // i can't use array_merge on DOMNodeLists :(
    $merged = function () use (&$domd): array {
        $ret = array ();
        foreach ( $domd->getElementsByTagName ( "input" ) as $input ) {
            $ret [] = $input;
        }
        foreach ( $domd->getElementsByTagName ( "textarea" ) as $textarea ) {
            $ret [] = $textarea;
        }
        foreach ( $domd->getElementsByTagName ( "button" ) as $button ) {
            $ret [] = $button;
        }
        return $ret;
    };
    $merged = $merged ();
    foreach ( $forms as $form ) {
        $inputs = function () use (&$domd, &$form, &$isDescendantOf, &$merged): array {
            $ret = array ();
            foreach ( $merged as $input ) {
                // hhb_var_dump ( $input->getAttribute ( "name" ), $input->getAttribute ( "id" ) );
                if ($input->hasAttribute ( "disabled" )) {
                    // ignore disabled elements?
                    continue;
                }
                $name = $input->getAttribute ( "name" );
                if ($name === '') {
                    // echo "inputs with no name are ignored when submitted by mainstream browsers (presumably because of specs)... follow suite?", PHP_EOL;
                    continue;
                }
                if (! $isDescendantOf ( $input, $form ) && $form->getAttribute ( "id" ) !== '' && $input->getAttribute ( "form" ) !== $form->getAttribute ( "id" )) {
                    // echo "this input does not belong to this form.", PHP_EOL;
                    continue;
                }
                if (! array_key_exists ( $name, $ret )) {
                    $ret [$name] = array (
                            $input 
                    );
                } else {
                    $ret [$name] [] = $input;
                }
            }
            return $ret;
        };
        $inputs = $inputs (); // sorry about that, Eclipse gets unstable on IIFE syntax.
        $hasName = true;
        $name = $form->getAttribute ( "id" );
        if ($name === '') {
            $name = $form->getAttribute ( "name" );
            if ($name === '') {
                $hasName = false;
            }
        }
        if (! $hasName) {
            $parsedForms [] = array (
                    $inputs 
            );
        } else {
            if (! array_key_exists ( $name, $parsedForms )) {
                $parsedForms [$name] = array (
                        $inputs 
                );
            } else {
                $parsedForms [$name] [] = $tmp;
            }
        }
    }
    unset ( $form, $tmp, $hasName, $name, $i, $input );
    if ($getOnlyFirstMatches) {
        foreach ( $parsedForms as $key => $val ) {
            $parsedForms [$key] = $val [0];
        }
        unset ( $key, $val );
        foreach ( $parsedForms as $key1 => $val1 ) {
            foreach ( $val1 as $key2 => $val2 ) {
                $parsedForms [$key1] [$key2] = $val2 [0];
            }
        }
    }
    if ($getElements) {
        return $parsedForms;
    }
    $ret = array ();
    foreach ( $parsedForms as $formName => $arr ) {
        $ret [$formName] = array ();
        foreach ( $arr as $ele ) {
            $ret [$formName] [$ele->getAttribute ( "name" )] = $ele->getAttribute ( "value" );
        }
    }
    return $ret;
}
  • 目前正在输出

    string(13) "login error: " string(26) "Invalid Login or password."

意思是??? / ???不是有效的用户名&amp;密码组合。

答案 2 :(得分:0)

在gitlab中创建access_token或private_token,您可以在URL中使用它。

bash <(curl -s原始网址的完整路径吗?private_token = your_private_token)param1 param2

答案 3 :(得分:0)

我得到了一个使用 Python、requestsbeautifulsoup4 的稳定版本:

from urllib.parse import urljoin

import requests
import bs4

base_uri = 'http://gitlab.example.com/'
username = 'me'
password = 'let_me_in'

# Use a session so cookies are retained between requests.
session = requests.session()

# Load the login page to get a CSRF token.
response = session.get(urljoin(base_uri, 'users/sign_in'))
response.raise_for_status()

# Extract the CSRF token from the login page.
soup = bs4.BeautifulSoup(response.text, 'html.parser')
csrf_param = soup.find('meta', dict(name='csrf-param'))['content']
csrf_token = soup.find('meta', dict(name='csrf-token'))['content']

# Login.
request_data = {
    'username': username,
    'password': password,
    csrf_param: csrf_token}
response = session.post(response.url, data=request_data)
response.raise_for_status()

# Get a list of project.
response = session.get(urljoin(base_uri, 'api/v4/projects'))
print(response.json())