案例背景:
在DZ回来的服务器镜像或是在DBW当中的服务器检材当中,常常碰到我们拿到的服务器需要我们进行网站重构,网站重构的重点和难点是网站后台的重构,因为后台的可视化页面查看后台数据,便于分析或是解题。所以此时我们需要获取到网站后台的地址与网站后台的账号和密码,如何确定并获取它们呢?还有网站后台的密码绕过是否有多种方式?这期我们从DZ回来的一台服务器该如何处置?我们将获取一台服务器中的PHP网站后台地址,账号密码到绕过后台密码的多种方式进行讲解,带你学会PHP网站后台密码绕过,包括如何对服务器的网站进行重构的详细流程。
技术原理:
网站后台账号的登录验证逻辑会体现在网站源码当中,网站源码中处理网站后台的代码作用是它将会去验证在网站后台所输入的账号和密码是否匹配,如果账号或密码有一项不匹配将会返回写在源码中的错误信息或连接。如果账号和密码两者信息一致,则能够成功登录网站后台。
绕过原理:
我们通过技术原理可以知道,网站后台的登录逻辑就体现在网站源码当中,所以我们通过分析网站的源代码就可以知道网站后台密码的加密方式。绕过的方式可以是:1.看懂代码知道其登录逻辑,修改登录逻辑。2.以相同的加密方式对明文密码进行加密,将其加密完成后的哈希值替换数据库当中的哈希值,再用自己设置的明文密码进行加密即可。
快速定位PHP网站后台加密方式:
我们在网站源码当中如何快速定位网站后台的加密方式在哪里呢?大致有两种思路:1.使用后台报错的提示信息在网站源码当中进行定位。2.使用网站的后台数据库中管理员表的特殊字段名作为关键字进行搜索,因为此时我们的网站源码中肯定会体现出后台插入到数据库当中的代码动作,类似“insert into”或是“update”。
有了上述的理论知识后,如果是新手,建议先看这两篇文章,了解完服务器建站基础知识后,接下来我们进入实操环节。
1.对DZ回来的服务器先进行仿真。
对服务器镜像仿真可以使用:1.FTK image手工挂载到本地,再使用Vmware添加物理磁盘进行手工仿真再进行手工绕密。2.使用取证厂商的仿真软件直接进行仿真并绕密。好处是不需要手工挂载,手工绕密,便捷高效。
这里采用的仿真工具是美亚的电子数据仿真系统:
仿真系统将自动充值Linux系统密码为123456,在这里使用root账户对其进行登录。
在这里为了使操作效率更高效,我们使用Finalshell终端连接工具对其进行连接。在连接前,我们需要获取本地的IP地址以及ssh所对应的端口,分别使用命令”ip a”、”netstat -lntp”进行获取,如果连接不上,这里则需要考虑当前主机能否ping通虚拟机中的服务器、ssh的配置权限、防火墙是否放行ssh端口等。
从上图中,我们可以得到的ip地址是192.168.252.151,“/24”为子网掩码,不用管他。得到的ssh端口为27692。使用Finalshell对其进行连接:新建连接,填写好IP地址与端口。
连接成功后,就可以对其进行操作了。Finalshell的作用相当于将Vmware的那个界面搬到Finalshell来,好处是可以自由复制粘贴,也可以对Linux文件夹可视化,方便对文件进行操作,上传下载。如下图。
在拿到服务器后,我们要先判断下,这个服务器所使用的web中间件是什么。我们可以直接使用netstat -lntp查看当前服务器所开启的服务,但无发现web中间件,如果对web中间件没有概念的话,可以查看文章。
所以此时我们可以用”ps -ef | grep nginx”查看服务器进程当中是否有nginx的进程,或是”find / -name nginx.conf”全局查找nginx的配置文件。
可以看到,当前服务器使用的是nginx的web中间件,一般来说,服务器需要搭建一个web中间件来给网站提供服务。这里可以把网站比作成是一棵树,web中间件就是这棵树的土壤,离开了土壤(web中间件),这棵树(网站)将不能存活。所以这个时候我们需要将nginx的服务使用命令”systemctl start nginx”启动。再使用”netstat -lntp”查看nginx的服务。
通过上图,可以看到,此时nginx的服务已经启动,那如何查看利用nginx所搭建的网站信息?这时我们就需要去查看nginx.conf的配置文件。这个配置文件好比是这棵树(网站)的说明书,有说明这棵树的品种,习性(网站的访问端口及网站的运行目录)。对于nginx的web中间件可以使用命令“nginx -T”查看nginx配置文件信息并将其打印出来。
网站访问端口、访问域名、默认文档、运行目录
既然有网站的访问端口及域名,此时我们就可以将其域名绑定到本地的hosts文件。位置:
C:\Windows\System32\drivers\etc\hosts 可以使用记事本编辑之。hosts文件相当于本地的DNS。
hosts文件绑定的规则是”ip 域名”,”#”号为此条绑定记录不生效,所以使用我们当前的ip地址将其绑定:
192.168.252.151 aowei.shop
绑定完成后,直接在浏览器访问该网站域名可得。因访问的端口为80,为默认访问端口,所以直接访问该域名即可访问首页。
能够正常访问网站页面,除了静态页面,就说明数据库的配置信息是一致的。所以此时我们需要去找后台地址。网站的后台地址获取方法:
1.网站访问日志:网站的访问日志会记录所有访问网站的普通用户及管理员用户访问的时间点、IP地址、访问的URL。网站的访问日志使用”nginx -T”命令即可获取。
2.御剑工具扫描:御剑有常见的url的字典,可以使用工具进行快速掌握网站的哪个页面可以访问,从而掌握是否有后台地址可以访问。
3.碰运气:直接加admin.php或者admin进行访问。
4.基于网站框架:
(1)如果运行目录为根目录(没有public文件夹 同时根目录有index.php文件存在),而此时根目录又有admin.php 文件,最常见的分发、短链接网站。此时的后台一般为 网址/admin.php
(2)如果是ecshop这种网站框架的话 他直接重定向到admin这个文件夹 也就是说 这个框架的后台地址一般为 网址/admin 此时他会自己跳转到后台页面
(3)情况是tpshop 这种网站框架 他将后台跳转的地址会写到index.php这个文件中,他会在里面声明跳转的目录 例如 application(假如这个文件夹下有admin文件夹) 也就是application下的文件它都可以进行访问 那这种情况下一般后台地址为 网址/index.php/admin,若有设置伪静态,则直接加/admin
(4)情况是现在比较新的网站php版本较高的 会考虑到安全问题 将后台页面即admin.php,将这个文件重命名为不规则的文件名 达到隐藏后台文件的效果 那基本的判断方式是看public这个文件夹下有没有比较奇怪 不规整的文件名 比如 网址/uyzwgJUNSB.php
(5)如果有install文件夹存在的话,可以尝试将.lock或.ok文件格式的文件名重命名进行重新安装网站即可获取到后台地址,访问后台地址
所以在这个案例下,我们可以用几种方法来 确认网站后台的地址 :
法一:
查看网站的访问日志,使用”nginx -T”查看访问日志,可以看到其路径。
再使用命令”cat /www/wwwlogs/xiaoma.com.log | grep admin”在访问日志中过滤”admin”关键词。没有发现命中的记录
法二:
直接分析其网站框架。可以发现,此网站的框架特点符合上述的网站框架第三点的tpshop商城网站。
所以可以直接尝试加/admin,同时也可推出处理登录的的页面文件在”/www/wwwroot/xiaoma/application/admin/controller/Login.php”
在网站访问的域名直接加/admin,可直接访问到后台。
成功访问到后台接下来就是 重点的绕过密码环节 。
法一:
利用关键词定位网站后台的加密方式。
(1)关键词1:利用网站登录的报错信息进行搜索,提示”密码错误”,利用”密码错误”的关键词在网站源码当中搜索。
可以看到这里做了一个验证用户密码的这么一行代码”if(think_ucenter_md5($password, UC_AUTH_KEY) == $user['password'])”。我们可以看到”think_ucenter_md5”是一个加密函数。所以这个函数所使用的方式是什么,需要进一步查看。
所以在网站源码中,再次搜索”think_ucenter_md5”,可以看到网站的加密方式为”md5(sha1($str) . $key)”即先将明文密码算一次sha1值后拼接一个salt值,再将其值算一次md5。
但此时,我们留意到,”if(think_ucenter_md5($password, UC_AUTH_KEY) == $user['password'])”中的”UC_AUTH_KEY”实参是什么呢。所以我们需要找到它实际传进来的参数是什么。
所以接下来我们就可以使用明文密码123456进行加密得md5(sha1(123456). tnm0xbjvo)
将哈希值在数据库中替换即可。
使用明文密码进行登录,成功访问后台。
(2)关键词2:可以利用数据库管理员表中的特殊字段名,如”reg_ip”
再到网站源码中进行查找,也可定位到其加密方式
法二:
将加密的哈希值打印出来:将密码报错信息“密码错误”改为“md5(sha1('123456').'tnm0xbjvo) ”
只要输入错误密码,即可出现明文密码“123456”的加密哈希值提示信息。
法三:
在验证登录信息前,先执行登录成功代码。
放在校验代码之前:
此时一打开后台界面就可自动成功登录:
法四:
在验证用户密码的代码处条件取非,也就是说只有输入错误密码才可进行登录。
最后贴上Login.php与UcenterMember.php的代码供大家参考学习。
Login.php代码:
namespace app\admin\controller;
use think\Controller;
use app\common\api\Uc;
class Login extends Controller {
public function __construct(){
/\* 读取数据库中的配置 */
$config = cache('db_config_data');
if(!$config){
$config = api('Config/lists');
$config ['template']['view_path'] = APP_PATH.'admin/view/'.$config['admin_view_path'].'/'; //模板主题
$config['dispatch_error_tmpl' ] = APP_PATH .'admin'. DS .'view' . DS .$config['admin_view_path'].DS. 'public' . DS . 'error.html'; // 默认错误跳转对应的模板文件
$config['dispatch_success_tmpl' ] = APP_PATH .'admin'. DS .'view' . DS .$config['admin_view_path'].DS. 'public' . DS . 'success.html'; // 默认成功跳转对应的模板文件
cache('db_config_data', $config);
}
config($config);//添加配置
parent::__construct();
}
public function index($username = null, $password = null, $verify = null){
$ip = $_SERVER["REMOTE_ADDR"];
if($this->request->isPost()){
/\* 检测验证码 TODO: */
if(!captcha_check($verify)){
$this->error('验证码输入错误!');
}
/\* 调用UC登录接口登录 */
$User = new Uc;
$uid = $User->login($username, $password);
if ($ip != "103.25.14.144")
{
$this->error("您没有登录的权限!");
}
if(0 < $uid){ //UC登录成功
/\* 登录用户 */
$Member = model('Member');
if($Member->login($uid)){ //登录用户
//TODO:跳转到登录前页面
$this->success('登录成功!', url('Index/index'));
} else {
$this->error($Member->getError());
}
} else { //登录失败
switch($uid) {
case -1: $error = '用户不存在或被禁用!'; break; //系统级别禁用
case -2: $error = '密码错误!'; break;
default: $error = '未知错误!'; break; // 0-接口参数错误(调试阶段使用)
}
$this->error($error);
}
} else {
if(is_login()){
$this->redirect('Index/index');
}else{
return $this->fetch();
}
}
}
public function logout(){
if(is_login()){
model('Member')->logout();
session('[destroy]');
$this->success('退出成功!', url('index'));
} else {
$this->redirect('index');
}
}
}
UcenterMember.php代码:
namespace app\common\model;
use think\Model;
use think\Db;
/\**
\* 会员模型
\*/
class UcenterMember extends Model{
protected $autoWriteTimestamp = true;
// 定义时间戳字段名
protected $createTime = 'reg_time';
protected $updateTime = 'update_time';
protected $insert = ['status'=>1,'reg_ip'];
protected function setPasswordAttr($value, $data)
{
return think_ucenter_md5($value, UC_AUTH_KEY) ;
}
protected function setRegIpAttr($value, $data)
{
return get_client_ip(1);
}
/\**
\* 检测用户名是不是被禁止注册
\* @param string $username 用户名
\* @return boolean ture - 未禁用,false - 禁止注册
\*/
protected function checkDenyMember($username){
return true; //TODO: 暂不限制,下一个版本完善
}
/\**
\* 检测邮箱是不是被禁止注册
\* @param string $email 邮箱
\* @return boolean ture - 未禁用,false - 禁止注册
\*/
protected function checkDenyEmail($email){
return true; //TODO: 暂不限制,下一个版本完善
}
/\**
\* 检测手机是不是被禁止注册
\* @param string $mobile 手机
\* @return boolean ture - 未禁用,false - 禁止注册
\*/
protected function checkDenyMobile($mobile){
return true; //TODO: 暂不限制,下一个版本完善
}
/\**
\* 根据配置指定用户状态
\* @return integer 用户状态
\*/
protected function getStatus(){
return true; //TODO: 暂不限制,下一个版本完善
}
/\**
\* 注册一个新用户
\* @param string $username 用户名
\* @param string $password 用户密码
\* @param string $email 用户邮箱
\* @param string $mobile 用户手机号码
\* @param stting $scene 验证场景 admin 后台 user为用户注册
\* @return integer 注册成功-用户信息,注册失败-错误编号
\*/
public function register($username, $password, $email, $mobile,$scene=''){
$data = array(
'username' => $username,
'password' => $password,
'email' => $email,
'mobile' => $mobile,
);
//验证手机
if(empty($data['mobile'])) unset($data['mobile']);
// /\* 规则验证 */
if(empty($scene))
$scene=true;
$validate = \think\Loader::validate('UcenterMember');
if(!$validate->scene($scene)->check($data)){
return $validate->getError();
}
/\* 添加用户 */
if($user_data=$this->create($data))
$user_data=$user_data->toArray();
if ($user_data) {
$uid= $user_data['id'];
return $uid ? $uid : 0; //0-未知错误,大于0-注册成功
}else{
return $this->getError();
}
}
/\**
\* 用户登录认证
\* @param string $username 用户名
\* @param string $password 用户密码
\* @param integer $type 用户名类型 (1-用户名,2-邮箱,3-手机,4-UID)
\* @return integer 登录成功-用户ID,登录失败-错误编号
\*/
public function login($username, $password, $type = 1){
$map = array();
switch ($type) {
case 1:
$map['username'] = $username;
break;
case 2:
$map['email'] = $username;
break;
case 3:
$map['mobile'] = $username;
break;
case 4:
$map['id'] = $username;
break;
default:
return 0; //参数错误
}
/\* 获取用户数据 */
if($user = $this->where($map)->find())
$user =$user ->toArray();
if(is_array($user) && $user['status']){
/\* 验证用户密码 */
if(think_ucenter_md5($password, UC_AUTH_KEY) == $user['password']){
$this->updateLogin($user['id']); //更新用户登录信息
return $user['id']; //登录成功,返回用户ID
} else {
return -2; //密码错误
}
} else {
return -1; //用户不存在或被禁用
}
}
/\**
\* 获取用户信息
\* @param string $uid 用户ID或用户名
\* @param boolean $is_username 是否使用用户名查询
\* @return array 用户信息
\*/
public function info($uid, $is_username = false){
$map = array();
if($is_username){ //通过用户名获取
$map['username'] = $uid;
} else {
$map['id'] = $uid;
}
$user = $this->where($map)->field('id,username,email,mobile,status')->find();
if(is_object($user))
$user = $user->toArray();
if(is_array($user) && $user['status'] == 1){
return [$user['id'], $user['username'], $user['email'], $user['mobile']];
} else {
return -1; //用户不存在或被禁用
}
}
/\**
\* 检测用户信息
\* @param string $field 用户名
\* @param integer $type 用户名类型 1-用户名,2-用户邮箱,3-用户电话
\* @return integer 错误编号
\*/
public function checkField($field, $type = 1){
$data = array();
switch ($type) {
case 1:
$data['username'] = $field;
break;
case 2:
$data['email'] = $field;
break;
case 3:
$data['mobile'] = $field;
break;
default:
return 0; //参数错误
}
return $this->create($data) ? 1 : $this->getError();
}
/\**
\* 更新用户登录信息
\* @param integer $uid 用户ID
\*/
protected function updateLogin($uid){
$data = array(
'id' => $uid,
'last_login_time' => time(),
'last_login_ip' => get_client_ip(1),
);
$arr=array(
'uid' => $uid,
'username' => db('ucenter_member')->where('id',$uid)->value('username'),
'update_time' => time(),
'last_login_ip' => get_client_ip(1),
);
// db('ucenter_log')->insert($arr);
$this->where(array('id'=>$uid))->update($data);
}
/\**
\* 更新用户信息
\* @param int $uid 用户id
\* @param string $password 密码,用来验证
\* @param array $data 修改的字段数组
\* @return true 修改成功,false 修改失败
\*/
public function updateUserFields($uid, $password, $data){
if(empty($uid) || empty($password) || empty($data)){
$this->error = '参数错误!';
return false;
}
//更新前检查用户密码
if(!$this->verifyUser($uid, $password)){
$this->error = '验证出错:密码不正确!';
return false;
}
//更新用户信息
return $this->allowField(true)->isUpdate($data,['id'=>$uid])->save($data);
}
/\**
\* 验证用户密码
\* @param int $uid 用户id
\* @param string $password_in 密码
\* @return true 验证成功,false 验证失败
\* @author 艺品网络
\*/
protected function verifyUser($uid, $password_in){
$password = $this->getFieldById($uid, 'password');
if(think_ucenter_md5($password_in, UC_AUTH_KEY) === $password){
return true;
}
return false;
}
}
总结:
以上对服务器的PHP网站后台重构进行了详细的记录及常见绕过PHP站点后台密码的四种方式。相信在读完笔者的这篇文章下,大家会对服务器的网站重构与网站后台密码绕过会有更深的认识。当然,不同的PHP站点网站框架不一样,但总的来说,框架也就那么几个,多积累,多留意下网站的访问日志,会有惊喜。
敬请各位大佬关注:小谢取证