Loading src/dsdb/secdesc.cpp +54 −4 Changes for src/dsdb/secdesc.cpp: 54 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -35,11 +35,12 @@ namespace { ace.push_back(char(a.flags)); put16(ace, 0); /* size, set below */ put32(ace, a.mask); if (a.type == ACCESS_ALLOWED_OBJECT) { if (a.type == ACCESS_ALLOWED_OBJECT || a.type == ACCESS_DENIED_OBJECT) { object = true; put32(ace, a.objectType.size() == 16 ? 1u : 0u); /* ACE_OBJECT_TYPE_PRESENT */ if (a.objectType.size() == 16) ace += a.objectType; bool ot = a.objectType.size() == 16, iot = a.inheritedObjectType.size() == 16; put32(ace, (ot ? 1u : 0u) | (iot ? 2u : 0u)); /* ACE_(INHERITED_)OBJECT_TYPE_PRESENT */ if (ot) ace += a.objectType; if (iot) ace += a.inheritedObjectType; } ace += sid; ace[2] = char(ace.size() & 0xff); Loading Loading @@ -83,6 +84,55 @@ std::string build(const Descriptor &d, uint32_t parts) { return o; } bool parse(const std::string &s, Descriptor &out) { out = Descriptor(); if (s.size() < 20 || s[0] != 1) return false; uint16_t control = get16(s, 2); if (!(control & SE_SELF_RELATIVE)) return false; out.daclProtected = (control & SE_DACL_PROTECTED) != 0; auto sidAt = [&](uint32_t off, Sid &sid) { if (!off || off >= s.size()) return; Sid::fromBinary(reinterpret_cast<const uint8_t*>(s.data()) + off, s.size() - off, sid); }; sidAt(get32(s, 4), out.owner); sidAt(get32(s, 8), out.group); uint32_t dacl = get32(s, 16); if (!(control & SE_DACL_PRESENT) || !dacl) return true; if (dacl + 8 > s.size()) return false; uint16_t count = get16(s, dacl + 4); size_t o = dacl + 8; for (uint16_t i = 0; i < count; ++i) { if (o + 8 > s.size()) return false; Ace a; a.type = uint8_t(s[o]); a.flags = uint8_t(s[o + 1]); uint16_t size = get16(s, o + 2); if (size < 8 || o + size > s.size()) return false; a.mask = get32(s, o + 4); size_t p = o + 8, end = o + size; if (a.type == ACCESS_ALLOWED_OBJECT || a.type == ACCESS_DENIED_OBJECT) { if (p + 4 > end) return false; uint32_t f = get32(s, p); p += 4; if (f & 1) { if (p + 16 > end) return false; a.objectType = s.substr(p, 16); p += 16; } if (f & 2) { if (p + 16 > end) return false; a.inheritedObjectType = s.substr(p, 16); p += 16; } } else if (a.type != ACCESS_ALLOWED && a.type != ACCESS_DENIED) { o = end; /* callback/audit ACEs: skipped */ continue; } size_t used = 0; if (!Sid::fromBinary(reinterpret_cast<const uint8_t*>(s.data()) + p, end - p, a.sid, &used)) return false; out.dacl.push_back(a); o = end; } return true; } std::string select(const std::string &s, uint32_t parts) { if (s.size() < 20 || s[0] != 1) return s; Loading src/dsdb/secdesc.h +6 −0 Changes for src/dsdb/secdesc.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ namespace fenrirds { ACCESS_ALLOWED = 0x00, ACCESS_DENIED = 0x01, ACCESS_ALLOWED_OBJECT = 0x05, ACCESS_DENIED_OBJECT = 0x06, }; enum AceFlags : uint8_t { OBJECT_INHERIT = 0x01, Loading Loading @@ -59,6 +60,8 @@ namespace fenrirds { /* ACCESS_ALLOWED_OBJECT: an extended right / property set * (16 bytes, Windows GUID byte order); empty = none */ std::string objectType; /* object ACEs: the object class the ACE is inherited to */ std::string inheritedObjectType; }; struct Descriptor { Loading @@ -69,6 +72,9 @@ namespace fenrirds { /* self-relative bytes; `parts` selects owner/group/dacl */ std::string build(const Descriptor &d, uint32_t parts = INFO_OWNER | INFO_GROUP | INFO_DACL); /* parse a self-relative SD (owner, group, DACL; the SACL is * dropped); false if it is not one */ bool parse(const std::string &sd, Descriptor &out); /* strip a stored self-relative SD down to the requested parts * (returns it unchanged if it cannot be parsed) */ std::string select(const std::string &sd, uint32_t parts); Loading src/grouppolicy.cpp +84 −0 Changes for src/grouppolicy.cpp: 84 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -412,6 +412,90 @@ bool Manager::setFlags(const std::string &guid, uint32_t flags, std::string &err return true; } namespace { bool isApplyAce(const dsdb::sd::Ace &a) { static const std::string apply = dsdb::sd::guidBytes(dsdb::sd::RIGHT_APPLY_GROUP_POLICY); return a.type == dsdb::sd::ACCESS_ALLOWED_OBJECT && (a.mask & dsdb::sd::DS_CONTROL_ACCESS) && a.objectType == apply; } /* a plain "Read" grant as GPMC writes it for a filter trustee */ bool isReadAce(const dsdb::sd::Ace &a) { return a.type == dsdb::sd::ACCESS_ALLOWED && a.mask == dsdb::sd::DS_GENERIC_READ; } } bool Manager::securityFilter(const std::string &guid, std::vector<dsdb::Sid> &out) { out.clear(); auto e = _Dir.byDn(gpoDn(guid)); if (!e) return false; dsdb::sd::Descriptor d; std::string raw = e->str("nTSecurityDescriptor"); if (!dsdb::sd::parse(raw, d)) return false; for (auto &a : d.dacl) if (isApplyAce(a) && std::find(out.begin(), out.end(), a.sid) == out.end()) out.push_back(a.sid); return true; } bool Manager::setSecurityFilter(const std::string &guid, const std::vector<dsdb::Sid> &sids, std::string &err) { auto e = _Dir.byDn(gpoDn(guid)); if (!e) { err = "no such GPO"; return false; } dsdb::sd::Descriptor d; if (!dsdb::sd::parse(e->str("nTSecurityDescriptor"), d)) { /* none or unreadable: start from the default */ dsdb::Sid domSid = _Dir.meta().sid; if (auto head = _Dir.byDn(_Dir.meta().baseDn)) if (!head->objectSid.empty()) domSid = head->objectSid; d = dsdb::sd::gpoContainer(domSid); } auto inNew = [&](const dsdb::Sid &s) { return std::find(sids.begin(), sids.end(), s) != sids.end(); }; std::vector<dsdb::Sid> old; for (auto &a : d.dacl) if (isApplyAce(a)) old.push_back(a.sid); const dsdb::Sid au = dsdb::wk::authenticatedUsers(), edc = dsdb::wk::enterpriseDCs(); std::vector<dsdb::sd::Ace> dacl; for (auto &a : d.dacl) { if (isApplyAce(a)) continue; /* re-added below */ /* the Read grant of a trustee leaving the filter goes with it */ if (isReadAce(a) && !inNew(a.sid) && a.sid != au && a.sid != edc && std::find(old.begin(), old.end(), a.sid) != old.end()) continue; dacl.push_back(a); } auto hasRead = [&](const dsdb::Sid &s) { for (auto &a : dacl) if (a.type == dsdb::sd::ACCESS_ALLOWED && a.sid == s && (a.mask & dsdb::sd::DS_READ_PROP) && (a.mask & dsdb::sd::DS_LIST)) return true; return false; }; auto readAce = [](const dsdb::Sid &s) { dsdb::sd::Ace a; a.sid = s; a.mask = dsdb::sd::DS_GENERIC_READ; a.flags = dsdb::sd::CONTAINER_INHERIT; return a; }; if (!hasRead(au)) dacl.push_back(readAce(au)); for (auto &s : sids) { if (!hasRead(s)) dacl.push_back(readAce(s)); dsdb::sd::Ace apply; apply.type = dsdb::sd::ACCESS_ALLOWED_OBJECT; apply.flags = dsdb::sd::CONTAINER_INHERIT; apply.mask = dsdb::sd::DS_CONTROL_ACCESS; apply.sid = s; apply.objectType = dsdb::sd::guidBytes(dsdb::sd::RIGHT_APPLY_GROUP_POLICY); dacl.push_back(apply); } d.dacl = dacl; dsdb::AttrMap a; a["nTSecurityDescriptor"] = {dsdb::sd::build(d)}; try { _Dir.modify(e->objectGuid, a, nullptr, true); } catch (const std::exception &ex) { err = ex.what(); return false; } return true; } std::vector<dsdb::Dn> Manager::linkTargets() { const dsdb::Dn &base = _Dir.meta().baseDn; std::vector<dsdb::Dn> out{base}; Loading src/grouppolicy.h +9 −0 Changes for src/grouppolicy.h: 9 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ #include <vector> #include "dsdb/dn.h" #include "dsdb/sid.h" namespace fenrirds { namespace dsdb { class Directory; } Loading Loading @@ -80,6 +81,14 @@ namespace fenrirds { bool registry(const std::string &guid, bool machine, std::vector<RegValue> &out); bool setRegistry(const std::string &guid, bool machine, const std::vector<RegValue> &values, std::string &err); /* security filtering: the trustees the GPO applies to (the ACEs * granting the Apply-Group-Policy right on its container) */ bool securityFilter(const std::string &guid, std::vector<dsdb::Sid> &out); /* make exactly `sids` the trustees: each gets Read + Apply Group * Policy; removed ones lose both. Authenticated Users keeps Read * in any case, so computers can still read the GPO (MS16-072) */ bool setSecurityFilter(const std::string &guid, const std::vector<dsdb::Sid> &sids, std::string &err); private: dsdb::Dn policiesDn() const; Loading Loading
src/dsdb/secdesc.cpp +54 −4 Changes for src/dsdb/secdesc.cpp: 54 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -35,11 +35,12 @@ namespace { ace.push_back(char(a.flags)); put16(ace, 0); /* size, set below */ put32(ace, a.mask); if (a.type == ACCESS_ALLOWED_OBJECT) { if (a.type == ACCESS_ALLOWED_OBJECT || a.type == ACCESS_DENIED_OBJECT) { object = true; put32(ace, a.objectType.size() == 16 ? 1u : 0u); /* ACE_OBJECT_TYPE_PRESENT */ if (a.objectType.size() == 16) ace += a.objectType; bool ot = a.objectType.size() == 16, iot = a.inheritedObjectType.size() == 16; put32(ace, (ot ? 1u : 0u) | (iot ? 2u : 0u)); /* ACE_(INHERITED_)OBJECT_TYPE_PRESENT */ if (ot) ace += a.objectType; if (iot) ace += a.inheritedObjectType; } ace += sid; ace[2] = char(ace.size() & 0xff); Loading Loading @@ -83,6 +84,55 @@ std::string build(const Descriptor &d, uint32_t parts) { return o; } bool parse(const std::string &s, Descriptor &out) { out = Descriptor(); if (s.size() < 20 || s[0] != 1) return false; uint16_t control = get16(s, 2); if (!(control & SE_SELF_RELATIVE)) return false; out.daclProtected = (control & SE_DACL_PROTECTED) != 0; auto sidAt = [&](uint32_t off, Sid &sid) { if (!off || off >= s.size()) return; Sid::fromBinary(reinterpret_cast<const uint8_t*>(s.data()) + off, s.size() - off, sid); }; sidAt(get32(s, 4), out.owner); sidAt(get32(s, 8), out.group); uint32_t dacl = get32(s, 16); if (!(control & SE_DACL_PRESENT) || !dacl) return true; if (dacl + 8 > s.size()) return false; uint16_t count = get16(s, dacl + 4); size_t o = dacl + 8; for (uint16_t i = 0; i < count; ++i) { if (o + 8 > s.size()) return false; Ace a; a.type = uint8_t(s[o]); a.flags = uint8_t(s[o + 1]); uint16_t size = get16(s, o + 2); if (size < 8 || o + size > s.size()) return false; a.mask = get32(s, o + 4); size_t p = o + 8, end = o + size; if (a.type == ACCESS_ALLOWED_OBJECT || a.type == ACCESS_DENIED_OBJECT) { if (p + 4 > end) return false; uint32_t f = get32(s, p); p += 4; if (f & 1) { if (p + 16 > end) return false; a.objectType = s.substr(p, 16); p += 16; } if (f & 2) { if (p + 16 > end) return false; a.inheritedObjectType = s.substr(p, 16); p += 16; } } else if (a.type != ACCESS_ALLOWED && a.type != ACCESS_DENIED) { o = end; /* callback/audit ACEs: skipped */ continue; } size_t used = 0; if (!Sid::fromBinary(reinterpret_cast<const uint8_t*>(s.data()) + p, end - p, a.sid, &used)) return false; out.dacl.push_back(a); o = end; } return true; } std::string select(const std::string &s, uint32_t parts) { if (s.size() < 20 || s[0] != 1) return s; Loading
src/dsdb/secdesc.h +6 −0 Changes for src/dsdb/secdesc.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ namespace fenrirds { ACCESS_ALLOWED = 0x00, ACCESS_DENIED = 0x01, ACCESS_ALLOWED_OBJECT = 0x05, ACCESS_DENIED_OBJECT = 0x06, }; enum AceFlags : uint8_t { OBJECT_INHERIT = 0x01, Loading Loading @@ -59,6 +60,8 @@ namespace fenrirds { /* ACCESS_ALLOWED_OBJECT: an extended right / property set * (16 bytes, Windows GUID byte order); empty = none */ std::string objectType; /* object ACEs: the object class the ACE is inherited to */ std::string inheritedObjectType; }; struct Descriptor { Loading @@ -69,6 +72,9 @@ namespace fenrirds { /* self-relative bytes; `parts` selects owner/group/dacl */ std::string build(const Descriptor &d, uint32_t parts = INFO_OWNER | INFO_GROUP | INFO_DACL); /* parse a self-relative SD (owner, group, DACL; the SACL is * dropped); false if it is not one */ bool parse(const std::string &sd, Descriptor &out); /* strip a stored self-relative SD down to the requested parts * (returns it unchanged if it cannot be parsed) */ std::string select(const std::string &sd, uint32_t parts); Loading
src/grouppolicy.cpp +84 −0 Changes for src/grouppolicy.cpp: 84 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -412,6 +412,90 @@ bool Manager::setFlags(const std::string &guid, uint32_t flags, std::string &err return true; } namespace { bool isApplyAce(const dsdb::sd::Ace &a) { static const std::string apply = dsdb::sd::guidBytes(dsdb::sd::RIGHT_APPLY_GROUP_POLICY); return a.type == dsdb::sd::ACCESS_ALLOWED_OBJECT && (a.mask & dsdb::sd::DS_CONTROL_ACCESS) && a.objectType == apply; } /* a plain "Read" grant as GPMC writes it for a filter trustee */ bool isReadAce(const dsdb::sd::Ace &a) { return a.type == dsdb::sd::ACCESS_ALLOWED && a.mask == dsdb::sd::DS_GENERIC_READ; } } bool Manager::securityFilter(const std::string &guid, std::vector<dsdb::Sid> &out) { out.clear(); auto e = _Dir.byDn(gpoDn(guid)); if (!e) return false; dsdb::sd::Descriptor d; std::string raw = e->str("nTSecurityDescriptor"); if (!dsdb::sd::parse(raw, d)) return false; for (auto &a : d.dacl) if (isApplyAce(a) && std::find(out.begin(), out.end(), a.sid) == out.end()) out.push_back(a.sid); return true; } bool Manager::setSecurityFilter(const std::string &guid, const std::vector<dsdb::Sid> &sids, std::string &err) { auto e = _Dir.byDn(gpoDn(guid)); if (!e) { err = "no such GPO"; return false; } dsdb::sd::Descriptor d; if (!dsdb::sd::parse(e->str("nTSecurityDescriptor"), d)) { /* none or unreadable: start from the default */ dsdb::Sid domSid = _Dir.meta().sid; if (auto head = _Dir.byDn(_Dir.meta().baseDn)) if (!head->objectSid.empty()) domSid = head->objectSid; d = dsdb::sd::gpoContainer(domSid); } auto inNew = [&](const dsdb::Sid &s) { return std::find(sids.begin(), sids.end(), s) != sids.end(); }; std::vector<dsdb::Sid> old; for (auto &a : d.dacl) if (isApplyAce(a)) old.push_back(a.sid); const dsdb::Sid au = dsdb::wk::authenticatedUsers(), edc = dsdb::wk::enterpriseDCs(); std::vector<dsdb::sd::Ace> dacl; for (auto &a : d.dacl) { if (isApplyAce(a)) continue; /* re-added below */ /* the Read grant of a trustee leaving the filter goes with it */ if (isReadAce(a) && !inNew(a.sid) && a.sid != au && a.sid != edc && std::find(old.begin(), old.end(), a.sid) != old.end()) continue; dacl.push_back(a); } auto hasRead = [&](const dsdb::Sid &s) { for (auto &a : dacl) if (a.type == dsdb::sd::ACCESS_ALLOWED && a.sid == s && (a.mask & dsdb::sd::DS_READ_PROP) && (a.mask & dsdb::sd::DS_LIST)) return true; return false; }; auto readAce = [](const dsdb::Sid &s) { dsdb::sd::Ace a; a.sid = s; a.mask = dsdb::sd::DS_GENERIC_READ; a.flags = dsdb::sd::CONTAINER_INHERIT; return a; }; if (!hasRead(au)) dacl.push_back(readAce(au)); for (auto &s : sids) { if (!hasRead(s)) dacl.push_back(readAce(s)); dsdb::sd::Ace apply; apply.type = dsdb::sd::ACCESS_ALLOWED_OBJECT; apply.flags = dsdb::sd::CONTAINER_INHERIT; apply.mask = dsdb::sd::DS_CONTROL_ACCESS; apply.sid = s; apply.objectType = dsdb::sd::guidBytes(dsdb::sd::RIGHT_APPLY_GROUP_POLICY); dacl.push_back(apply); } d.dacl = dacl; dsdb::AttrMap a; a["nTSecurityDescriptor"] = {dsdb::sd::build(d)}; try { _Dir.modify(e->objectGuid, a, nullptr, true); } catch (const std::exception &ex) { err = ex.what(); return false; } return true; } std::vector<dsdb::Dn> Manager::linkTargets() { const dsdb::Dn &base = _Dir.meta().baseDn; std::vector<dsdb::Dn> out{base}; Loading
src/grouppolicy.h +9 −0 Changes for src/grouppolicy.h: 9 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ #include <vector> #include "dsdb/dn.h" #include "dsdb/sid.h" namespace fenrirds { namespace dsdb { class Directory; } Loading Loading @@ -80,6 +81,14 @@ namespace fenrirds { bool registry(const std::string &guid, bool machine, std::vector<RegValue> &out); bool setRegistry(const std::string &guid, bool machine, const std::vector<RegValue> &values, std::string &err); /* security filtering: the trustees the GPO applies to (the ACEs * granting the Apply-Group-Policy right on its container) */ bool securityFilter(const std::string &guid, std::vector<dsdb::Sid> &out); /* make exactly `sids` the trustees: each gets Read + Apply Group * Policy; removed ones lose both. Authenticated Users keeps Read * in any case, so computers can still read the GPO (MS16-072) */ bool setSecurityFilter(const std::string &guid, const std::vector<dsdb::Sid> &sids, std::string &err); private: dsdb::Dn policiesDn() const; Loading