Skip to content

Authentication and Cookies

Patterns for cases that require a login.

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin'),
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
)

If HttpUsername / HttpPassword are set, a retry after a 401 response may select Basic, Digest, NTLM, or another scheme. Use authorization or preferred_auth when you want to specify the method explicitly.

res = send_request_cgi(
'method' => 'GET',
'uri' => '/',
'username' => datastore['HttpUsername'],
'password' => datastore['HttpPassword'],
'preferred_auth' => 'Digest'
)

Examples of preferred_auth: Basic, Digest, NTLM, Kerberos, Negotiate, None.

res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'login.php'),
'keep_cookies' => true,
'vars_post' => {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
}
)
fail_with(Failure::Unreachable, "#{peer} - no response") if res.nil?
fail_with(Failure::NoAccess, "login failed: #{res.code}") unless res.code == 302
# The cookie_jar is attached automatically from here on
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'dashboard')
)

example_webapp.rb follows the same flow. Some examples use Basic and form authentication together.

'authorization' => basic_auth(user, pass),
'keep_cookies' => true,
'vars_post' => { 'username' => user, 'password' => pass }

For an application with public registration, create a new account and then log in. If the registration response contains no session cookie, keep_cookies is only needed for the login. Saved cookies are attached automatically to subsequent requests.

def authenticate
return true if @authenticated
user = datastore['USERNAME']
pass = datastore['PASSWORD']
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'register'),
'vars_post' => { 'username' => user, 'password' => pass }
)
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'login'),
'keep_cookies' => true,
'vars_post' => { 'username' => user, 'password' => pass }
)
fail_with(Failure::NoAccess, 'login failed') if res.nil?
fail_with(Failure::NoAccess, "login failed: HTTP #{res.code}") unless res.code == 200 || res.redirect?
@authenticated = true
true
end
  • If the registration response includes Set-Cookie, add keep_cookies to the registration POST as well.
  • Check the registration and login results using the response code or redirect destination.
  • If CSRF is required, make a GET request first to obtain the token, as in the next section.
  • When using AutoCheck, call this function at the entry points of check and exploit. @authenticated prevents registration and login from repeating during the same run. See “Self-registration → login → JSON API RCE” in Exploit for an implementation example.

Reference modules: modules/exploits/unix/webapp/byob_unauth_rce.rb (separate register_user and login_user methods) and modules/exploits/linux/http/xplico_exec.rb.

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'users', 'sign_in')
)
fail_with(Failure::Unreachable, 'no response') if res.nil?
token = res.body[%r{name="authenticity_token" value="([^"]+)"}, 1]
fail_with(Failure::UnexpectedReply, 'CSRF token missing') if token.blank?
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'users', 'sign_in'),
'keep_cookies' => true,
'vars_post' => {
'authenticity_token' => token,
'user[login]' => datastore['USERNAME'],
'user[password]' => datastore['PASSWORD'],
'user[remember_me]' => '0'
}
)

Fix the cookie for this request alone without using the jar.

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin'),
'cookie' => "PHPSESSID=#{@phpsessid}; role=admin"
)

For a request with cookie, the specified value takes precedence over automatic attachment from the jar for that request.

cj = Msf::Exploit::Remote::HTTP::HttpCookieJar.new
host = datastore['RHOST']
cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('PHPSESSID', @phpsessid, domain: host, path: '/'))
cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('role', 'admin', domain: host, path: '/'))
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin'),
'cookie' => cj
)

domain and path are required. Their values are Strings.

Section titled “Clearing the cookie jar and handling expiration”
cookie_jar.clear
send_request_cgi(
'method' => 'GET',
'uri' => '/',
'expire_cookies' => false
)

Call cookie_jar.clear before logging in as another account.

Section titled “Classic pattern: extract Set-Cookie manually”
res = send_request_cgi(...)
cookie = res.get_cookies
send_request_cgi(
'method' => 'GET',
'uri' => '/next',
'cookie' => cookie
)

For new modules, keep_cookies is more concise.