fix(accounts): prevent SQL injection in MSSQL account automation

This commit is contained in:
Crane.z
2026-07-01 17:39:17 +08:00
parent e3f7fafca2
commit 8fe9f267ff
4 changed files with 147 additions and 8 deletions

View File

@@ -33,7 +33,9 @@
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
script: "SELECT 1 FROM sys.sql_logins WHERE name=%(username)s;"
params:
username: "{{ account.username }}"
when: db_info is succeeded
register: user_exist
@@ -46,7 +48,35 @@
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
script: |
DECLARE @login_name nvarchar(256) = %(username)s;
DECLARE @password nvarchar(4000) = %(password)s;
DECLARE @db_name nvarchar(256) = %(db_name)s;
DECLARE @sql nvarchar(max);
IF @login_name IS NULL OR LEN(@login_name) = 0 OR LEN(@login_name) > 128
BEGIN
RAISERROR('Invalid login name', 16, 1);
RETURN;
END;
IF @db_name IS NULL OR LEN(@db_name) = 0 OR LEN(@db_name) > 128 OR DB_ID(@db_name) IS NULL
BEGIN
RAISERROR('Invalid default database', 16, 1);
RETURN;
END;
-- 标识符使用 QUOTENAME 防止名称逃逸,密码在拼接前转义单引号。
SET @sql = N'ALTER LOGIN ' + QUOTENAME(@login_name)
+ N' WITH PASSWORD = N''' + REPLACE(@password, N'''', N'''''') + N''', DEFAULT_DATABASE = '
+ QUOTENAME(@db_name) + N';';
EXEC sys.sp_executesql @sql;
SELECT @@VERSION AS version;
params:
username: "{{ account.username }}"
password: "{{ account.secret }}"
db_name: "{{ jms_asset.spec_info.db_name }}"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
@@ -59,7 +89,43 @@
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "CREATE LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; CREATE USER {{ account.username }} FOR LOGIN {{ account.username }}; select @@version"
script: |
DECLARE @login_name nvarchar(256) = %(username)s;
DECLARE @password nvarchar(4000) = %(password)s;
DECLARE @db_name nvarchar(256) = %(db_name)s;
DECLARE @sql nvarchar(max);
IF @login_name IS NULL OR LEN(@login_name) = 0 OR LEN(@login_name) > 128
BEGIN
RAISERROR('Invalid login name', 16, 1);
RETURN;
END;
IF @db_name IS NULL OR LEN(@db_name) = 0 OR LEN(@db_name) > 128 OR DB_ID(@db_name) IS NULL
BEGIN
RAISERROR('Invalid default database', 16, 1);
RETURN;
END;
-- 标识符使用 QUOTENAME 防止名称逃逸,密码在拼接前转义单引号。
SET @sql = N'CREATE LOGIN ' + QUOTENAME(@login_name)
+ N' WITH PASSWORD = N''' + REPLACE(@password, N'''', N'''''') + N''', DEFAULT_DATABASE = '
+ QUOTENAME(@db_name) + N';';
EXEC sys.sp_executesql @sql;
IF USER_ID(@login_name) IS NULL
BEGIN
SET @sql = N'CREATE USER ' + QUOTENAME(@login_name)
+ N' FOR LOGIN ' + QUOTENAME(@login_name) + N';';
EXEC sys.sp_executesql @sql;
END;
SELECT @@VERSION AS version;
params:
username: "{{ account.username }}"
password: "{{ account.secret }}"
db_name: "{{ jms_asset.spec_info.db_name }}"
ignore_errors: true
when: user_exist.query_results[0] | length == 0

View File

@@ -33,7 +33,9 @@
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "SELECT 1 from sys.sql_logins WHERE name='{{ account.username }}';"
script: "SELECT 1 FROM sys.sql_logins WHERE name=%(username)s;"
params:
username: "{{ account.username }}"
when: db_info is succeeded
register: user_exist
@@ -46,7 +48,35 @@
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}', DEFAULT_DATABASE = {{ jms_asset.spec_info.db_name }}; select @@version"
script: |
DECLARE @login_name nvarchar(256) = %(username)s;
DECLARE @password nvarchar(4000) = %(password)s;
DECLARE @db_name nvarchar(256) = %(db_name)s;
DECLARE @sql nvarchar(max);
IF @login_name IS NULL OR LEN(@login_name) = 0 OR LEN(@login_name) > 128
BEGIN
RAISERROR('Invalid login name', 16, 1);
RETURN;
END;
IF @db_name IS NULL OR LEN(@db_name) = 0 OR LEN(@db_name) > 128 OR DB_ID(@db_name) IS NULL
BEGIN
RAISERROR('Invalid default database', 16, 1);
RETURN;
END;
-- 标识符使用 QUOTENAME 防止名称逃逸,密码在拼接前转义单引号。
SET @sql = N'ALTER LOGIN ' + QUOTENAME(@login_name)
+ N' WITH PASSWORD = N''' + REPLACE(@password, N'''', N'''''') + N''', DEFAULT_DATABASE = '
+ QUOTENAME(@db_name) + N';';
EXEC sys.sp_executesql @sql;
SELECT @@VERSION AS version;
params:
username: "{{ account.username }}"
password: "{{ account.secret }}"
db_name: "{{ jms_asset.spec_info.db_name }}"
ignore_errors: true
when: user_exist.query_results[0] | length != 0
register: change_info
@@ -60,7 +90,34 @@
name: '{{ jms_asset.spec_info.db_name }}'
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "CREATE LOGIN [{{ account.username }}] WITH PASSWORD = '{{ account.secret }}'; CREATE USER [{{ account.username }}] FOR LOGIN [{{ account.username }}]; select @@version"
script: |
DECLARE @login_name nvarchar(256) = %(username)s;
DECLARE @password nvarchar(4000) = %(password)s;
DECLARE @sql nvarchar(max);
IF @login_name IS NULL OR LEN(@login_name) = 0 OR LEN(@login_name) > 128
BEGIN
RAISERROR('Invalid login name', 16, 1);
RETURN;
END;
-- 标识符使用 QUOTENAME 防止名称逃逸,密码在拼接前转义单引号。
SET @sql = N'CREATE LOGIN ' + QUOTENAME(@login_name)
+ N' WITH PASSWORD = N''' + REPLACE(@password, N'''', N'''''') + N''';';
EXEC sys.sp_executesql @sql;
IF USER_ID(@login_name) IS NULL
BEGIN
SET @sql = N'CREATE USER ' + QUOTENAME(@login_name)
+ N' FOR LOGIN ' + QUOTENAME(@login_name) + N';';
EXEC sys.sp_executesql @sql;
END;
SELECT @@VERSION AS version;
params:
username: "{{ account.username }}"
password: "{{ account.secret }}"
ignore_errors: true
when: user_exist.query_results[0] | length == 0
register: change_info

View File

@@ -13,5 +13,20 @@
name: "{{ jms_asset.spec_info.db_name }}"
encryption: "{{ jms_asset.encryption | default(None) }}"
tds_version: "{{ jms_asset.tds_version | default(None) }}"
script: "DROP LOGIN {{ account.username }}; select @@version"
script: |
DECLARE @login_name nvarchar(256) = %(username)s;
DECLARE @sql nvarchar(max);
IF @login_name IS NULL OR LEN(@login_name) = 0 OR LEN(@login_name) > 128
BEGIN
RAISERROR('Invalid login name', 16, 1);
RETURN;
END;
-- 标识符使用 QUOTENAME 防止 login name 逃逸。
SET @sql = N'DROP LOGIN ' + QUOTENAME(@login_name) + N';';
EXEC sys.sp_executesql @sql;
SELECT @@VERSION AS version;
params:
username: "{{ account.username }}"

View File

@@ -280,7 +280,8 @@ def main():
login_port=dict(type='int', default=1433),
script=dict(required=True),
output=dict(default='default', choices=['dict', 'default']),
params=dict(type='dict'),
# 防止 params 中的密码出现在日志中
params=dict(type='dict', no_log=True),
transaction=dict(type='bool', default=True),
tds_version=dict(type='str', required=False, default=None),
encryption=dict(type='str', required=False, default=None)