Hello,
I'm seeing this error when running the code on SQL 2016. On SQL 2017 it works fine.
Argument 2 of the "JSON_VALUE or JSON_QUERY" must be a string literal.
I updated the 2nd argument to a string literal and it fixed the error and works fine.
But am I open to SQL Injection now due to this? Can someone explain?
Working Code:
public async Task<IList<FileSystemItemJsonDTO>> GetFileSystemItems(int moduleId, IDictionary<string, string> metadata) { var sqlParams = new List<SqlParameter>(); StringBuilder sb = new StringBuilder(); // start the initial select query... sb.Append("SELECT * FROM dbo.FileSystemItems WHERE "); int counter = 0; foreach (var item in metadata) { // only add an AND if we are NOT the first record... if (counter != 0) { sb.Append(" AND "); } // setup our json path and value items... string valueParam = string.Format(CultureInfo.CurrentCulture, "jsonPathValue{0}", counter); // 2nd item for JSON_VALUE has to be string literal for SQL server 2016 sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, '$.{0}') = @{1}", item.Key, valueParam); // add in our parameters to assist with sql injection sqlParams.Add(new SqlParameter(valueParam, string.Format(CultureInfo.CurrentCulture, "{0}", item.Value))); counter++; } return await BIContext.FileSystemItems .Where(x => x.ModuleId == moduleId) .FromSql(sb.ToString(), sqlParams.ToArray()) .Select(s => new FileSystemItemJsonDTO { FileId = s.FileId, FileName = s.FileName, FileType = s.FileType, LastWriteTime = s.LastWriteTime, FileSystemItemDataId = s.FileSystemItemDataId, ModuleId = moduleId, FileMetadata = s.FileMetadata, FileSize = s.FileSize }) .ToListAsync().ConfigureAwait(false); }
Failing Code:
public async Task<IList<FileSystemItemJsonDTO>> GetFileSystemItems(int moduleId, IDictionary<string, string> metadata) { var sqlParams = new List<SqlParameter>(); StringBuilder sb = new StringBuilder(); // start the initial select query... sb.Append("SELECT * FROM dbo.FileSystemItems WHERE "); int counter = 0; foreach (var item in metadata) { // only add an AND if we are NOT the first record... if (counter != 0) { sb.Append(" AND "); } // setup our json path and value items... string pathParam = string.Format(CultureInfo.CurrentCulture, "jsonPathParam{0}", counter); string valueParam = string.Format(CultureInfo.CurrentCulture, "jsonPathValue{0}", counter); sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, @{0}) = @{1}", pathParam, valueParam); // add in our parameters to assist with sql injection sqlParams.Add(new SqlParameter(pathParam, string.Format(CultureInfo.CurrentCulture, "$.{0}", item.Key))); sqlParams.Add(new SqlParameter(valueParam, item.Value)); counter++; } return await BIContext.FileSystemItems .Where(x => x.ModuleId == moduleId) .FromSql(sb.ToString(), sqlParams.ToArray()) .Select(s => new FileSystemItemJsonDTO { FileId = s.FileId, FileName = s.FileName, FileType = s.FileType, LastWriteTime = s.LastWriteTime, FileSystemItemDataId = s.FileSystemItemDataId, ModuleId = moduleId, FileMetadata = s.FileMetadata, FileSize = s.FileSize }) .ToListAsync().ConfigureAwait(false); }
Notice the difference on this line:
// 2nd item for JSON_VALUE has to be string literal for SQL server 2016 sb.AppendFormat(CultureInfo.CurrentCulture, "JSON_VALUE(FileMetadata, '$.{0}') = @{1}", item.Key, valueParam);